NSTokenField and deleting tokens

2020-02-11 09:14发布

问题:

My app allows users to attach tags to certain model objects (subclasses of NSManagedObject). The Tag class is also a subclass of NSManagedObject. I decided to use NSTokenField to display the tags, where each token holds an instance of Tag as the represented object. It all works pretty good but I'm stuck in situations where the user deletes a token as I want to check whether the associated Tag has become obsolete and should be deleted.

I was expecting a method in NSTokenFieldDelegate or NSTokenFieldCellDelegate which would allow me to intercept and check a delete action on a token.

After some googling I found this post addressing the topic. I implemented the suggested method controlTextDidChange: in my controller (the delegate of the token field). Upon inspecting the control that is passed as an argument, it revealed to be an instance of NSTokenTextView for which I cannot find any documentation (probably a private class).

Has anybody run into this and found a solution to gracefully delete tokens while maintaining the underlying model of represented objects?

EDIT

I found this as well, which seems to suggest that for some reason it just is not designed to work like the rest of us would expect.

回答1:

You should be able to simulate a delete delegate by creating a token wrapper class that has a pointer back to the owner as well as the wrapped object:

@protocol TokenWrapperDelegate 
-(void)tokenWasDeleted:(id)token;
@end

@interface TokenWrapper : NSObject {
  id<TokenWrapperDelegate> owner;
  id token;
}
-(id)initWithWrappedToken:(id)token owner:(id<TokenWrapperDelegate>)owner;
@property (nonatomic, weak) id<TokenWrapperDelegate> owner;
@property (nonatomic, strong) id token;
@end

Then have the TokenWrapper dealloc notify the owner that the token was deleted:

@implementation TokenWrapper

...

-(void)dealloc {
  [owner tokenWasDeleted:self.token];
  self.token = nil;
  [super dealloc];
}

@end

Then in your representedObjectForEditingString callback, return an autoreleased wrapper pointing at your owner and your real token. You'll also have to make sure to change the other NSTokenField delegate callbacks to delve into the wrapper object. Make sure the owner sets a bit to ignore these callbacks when you're manually changing the contents of the NSTokenField (like by calling setObjectValue).



回答2:

I gave up (after stumbling around for more than 6 hours) on the approach of in place editing my tags using NSTokenField. I eventually ended up with a number of fragile hacks which would ripple through my application as this feature is needed in various places.

Unless somebody has some strong points to counter my current opinion, NSTokenField is a bit of an ugly monster bringing a half baked implementation to the party. Which is a shame as the presentation side of it really appeals to me...

EDIT: After some further experimenting, I settled on a reasonably acceptable compromise. I use NSTokenField in a readonly mode. It takes the relevant tags from my Core Data store and displays them as tokens. I added a menu to each token which allows the user to edit, delete or review a tag. A standard push button next to the token field allows to add a new tag. Editing and reviewing is implemented using NSPopovers. See this example:

There are still some minor issues:

  • The tokens tend to disappear at arbitrary times when hovering the mouse over the token field. This appears to be a bug.
  • As the token field only accepts NSArray for its binding, I introduced a "virtual property" named tagsAsArray that takes the associated tags and converts them from NSSet to NSArray. I think this impacts the KVO as edits of tags only show up after pressing enter or clicking outside the token field.