I want to have an NSTokenField
that contains both plain text and tokens. That's the same problem as in this question, but the answers there haven't solved it for me. Maybe I'm missing something, or maybe Apple changed something in the 5 years since those answers were posted.
Specifically, let's say I want to type "hello%tok%" and have it turn into this:
In order to try to remove chances for confusion, I always use a custom represented object, of one of the following classes, rather than a plain string...
@interface Token : NSObject
@end
@implementation Token
@end
@interface WrappedString : NSObject
@property (retain) NSString* text;
@end
@implementation WrappedString
@end
Here are my delegate methods:
- (NSString *)tokenField:(NSTokenField *)tokenField
displayStringForRepresentedObject:(id)representedObject
{
NSString * displayString = nil;
if ([representedObject isKindOfClass: [WrappedString class]])
{
displayString = ((WrappedString*)representedObject).text;
}
else
{
displayString = @"TOKEN";
}
return displayString;
}
- (NSTokenStyle)tokenField:(NSTokenField *)tokenField
styleForRepresentedObject:(id)representedObject
{
NSTokenStyle theStyle = NSPlainTextTokenStyle;
if ([representedObject isKindOfClass: [Token class]])
{
theStyle = NSRoundedTokenStyle;
}
return theStyle;
}
- (NSString *)tokenField:(NSTokenField *)tokenField
editingStringForRepresentedObject:(id)representedObject
{
NSString * editingString = representedObject;
if ([representedObject isKindOfClass: [Token class]])
{
editingString = nil;
}
else
{
editingString = ((WrappedString*)representedObject).text;
}
return editingString;
}
- (id)tokenField:(NSTokenField *)tokenField
representedObjectForEditingString:(NSString *)editingString
{
id repOb = nil;
if ([editingString isEqualToString:@"tok"])
{
repOb = [[[Token alloc] init] autorelease];
}
else
{
WrappedString* wrapped = [[[WrappedString alloc]
init] autorelease];
wrapped.text = editingString;
repOb = wrapped;
}
return repOb;
}
As I'm typing the "hello", none of the delegate methods is called, which seems reasonable. When I type the first "%", there are 3 delegate calls:
tokenField:representedObjectForEditingString:
gets the string "hello" and turns it into a WrappedString
representation.tokenField:styleForRepresentedObject:
gets that WrappedString
and returns NSPlainTextTokenStyle
.tokenField:editingStringForRepresentedObject:
gets the WrappedString
and returns "hello".The first two calls seem reasonable. I'm not sure about number 3, because the token should be editable but it's not being edited yet. I would have thought that tokenField:displayStringForRepresentedObject:
would get called, but it doesn't.
When I type "tok", no delegate methods are called. When I type the second "%", tokenField:representedObjectForEditingString:
receives the string "hellotok", where I would have expected to see just "tok". So I never get a chance to create the rounded token.
If I type the text in the other order, "%tok%hello", then I do get the expected result, a round token followed by plain "hello".
By the way, the Token Field Programming Guide says
Note that there can be only one token per token field that is configured for the plain-text token style.
which seems to imply that it's not possible to freely mix plain text and tokens.
I asked myself whether I had seen mixed text and tokens anywhere in standard apps, and I had. In the Language & Text panel of System Preferences, under the Formats tab, clicking one of the "Customize..." buttons brings up a dialog containing token fields. Here's part of one.
Here, you don't create tokens by typing a tokenizing character, you drag and drop prototype tokens.
To make one of the prototype tokens, make another NSTokenField
and set it to have no background or border and be selectable but not editable. When your window has loaded, you can initialize the prototype field using the objectValue
property, e.g.,
self.protoToken.objectValue = @[[[[Token alloc] init] autorelease]];
You need to set up a delegate for each prototype token field as well as your editable token field. In order to be able to drag and drop tokens, your delegate must implement tokenField:writeRepresentedObjects:toPasteboard:
and tokenField:readFromPasteboard:
.