NSTokenField
NSTokenField
Mithilfe des NSTokenField kann man recht elegant Texteingaben mit Schlüsselwörtern mischen. Damit muss der Anwender keine für ihn kryptischen Steuersymbole wie %VORNAME% oder @Firstname@ verwenden, sondern kann die Schlüsselwörter, die durch eine weitere Operation mit Werten gefüllt werden, mit der Maus einsetzen:
Das NSTokenfield geht von einer Liste von Token-Objekten aus. Ohne weitere Konfiguration gibt das Komma oder Line-Feed als Trennungsymbol. Token werden dann im NSRoundedTokenStyle Stil angezeigt. Das ist für das obige Beispiel nicht erwünscht. Um Schlüsselwörter und Texte zu mischen, muss man zwei Arten von Token verwalten, die als Liste durch das NSTokenField verwaltet werden:
- Schlüsselwort-Token
- Freitext-Token
Anpassung der Darstellung
Durch die folgende Delegate-Methode wechseln wir die Darstellungsoptionen der beiden Arten von Token ab:
- (NSTokenStyle)tokenField:(NSTokenField *)tokenField
styleForRepresentedObject:(id)representedObject {
NSTokenStyle result= NSPlainTextTokenStyle;
if( self.messageExpression == tokenField ) {
if( [representedObject isKindOfClass:[MessageToken class]] )
result= NSRoundedTokenStyle;
} // if
else
result= NSRoundedTokenStyle;
return result;
}
Die Schlüsselwort-Token erhalten einen gerundeten Rahmen mit blauem Hintergrund. Die Freitext-Token werden als Texte dargestellt. Schön wäre es noch, wenn man die Farben einstellen könnte. Dies ist aber zur Zeit nicht möglich.
MessageToken ist eine eigene Klasse, die die Schlüsselwörter kapselt. Gleichzeitig dient die Klasse zur Unterscheidung der beiden Token-Arten.
Letztendlich bleibt die Eingabe des NSTokenFields ein String, der die Schlüsslwörter mit Trennungsymbolen enthält: @User@^:^@Project@^ | ^@Task@^ | ^@Time@ dabei gilt:
- ^ ist das Trennungssymbol zwischen den beiden Token
- @User@, @Project@, @Task@, @Time@ sind Schlüsselwort-Token
- alles andere sind Freitext-Token
- @User@
- :
- @Project@
- |
- @Task@
- |
- @Time@
- (NSArray *)tokenField:(NSTokenField *)tokenField
readFromPasteboard:(NSPasteboard *)pboard {
static NSArray* _classes;
if( !_classes ) {
_classes= [[NSArray alloc] initWithObjects:[NSString class],nil];
} // if
NSArray* result= [[pboard readObjectsForClasses:_classes
options:[NSDictionary dictionary]] map:^(id object) {
id tempResult= [MessageToken findMessageTokenForString:object];
if( !tempResult )
tempResult= object;
return tempResult;
}];
return result;
}
Zunächst werden die einzelnen Token-Strings vom NSPasteboard gelesen. Diese befinden sich dann in einem Array. Anschließend wird jedes Objekt des Arrays abgebildet (map). Dabei handelt es sich um eine NSArray-Erweiterung, die einen Block erwartet, in der die Abbildung definiert wird. Die Klassenfunktion findMessageTokenForString versucht ein Message-Token zu finden. Falls nicht, dann wird das Objekt zurückgeliefert. Das Ergebnis ist also ein Liste von NSString- und MessageToken-Objekten.
Block
Und wem die map-Methode nicht geheuer ist, hier die Kategorie-Erweiterung für NSArray:
- (NSArray*)map:(id(^)(id object))filterBlock {
NSMutableArray* result= [NSMutableArray array];
for(id object in self) {
id mappedObject= filterBlock( object );
if( mappedObject ) {
[result addObject:mappedObject];
} // if
} // for
return result;
}
CoreData und Bindings
Im Zusammenhang mit CoreData und den Bindings wird zur Unterstützung des NSTokenFields noch ein Value-Transformer benötigt:
@implementation MessageTokenTransformer
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return YES;
}
- (id)transformedValue:(id)value {
NSArray* tokens= [(NSString*)value componentsSeparatedByString:@"^"];
NSArray* result= [tokens map:^(id object) {
id tempResult= [MessageToken findMessageTokenForString:object];
if( !tempResult )
tempResult= object;
return tempResult;
}];
return result;
}
- (id)reverseTransformedValue:(id)value {
NSMutableString* result= [NSMutableString string];
for( id object in value ) {
if( [result length] > 0 )
[result appendString:@"^"];
if( [object isKindOfClass:[MessageToken class]] ) {
[result appendString:[object token]];
} // if
else {
[result appendString:[object description]];
} // else
} // for
return result;
}
@end
<< Home