Sonntag, Februar 07, 2010

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
Die obige String wird in eine Liste von Token verarbeitet:
  • @User@
  • :
  • @Project@
  • |
  • @Task@
  • |
  • @Time@
Dabei wird der Delegate für jedes Token gefragt, was er damit machen soll und welches Objekt es repräsentiert. Hierzu ein Beispiel. Es wird ein String über das NSPasteboard hinzugefügt. In diesem Falls soll der Delegate die Liste der repräsentierten Objekten erstellen. Das Ergebnis wird ein NSArray sein, dass je nach Token, MessageToken- oder NSString-Objekte enthält:

- (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

Dienstag, Februar 02, 2010

Gruppenkalendar durch Stellvertreter

Apple's Calendar Server

Im aktuellen Entwicklungszweig befindet sich ein Calendar-Server, der zwar keinen Gruppenkalendar mehr anbietet, dafür die Unterstützung von Stellvertretern für einen gemeinsamen Kalendar. Somit lässt sich die Gruppenfunktionalität einrichten.

Proxys

Dazu richtet man eine Gruppe mit den Stellvertretern und eine Stellvertreterkonfiguration mit der Gruppe ein. Das ganze geschieht in der proxies.xml-Datei. Die Stellvertreterregelung wird auch von iCal.app unterstützt. Insgesamt macht die aktuelle Entwicklerversion einen guten Eindruck. Nach Eingabe von ./run -s werden alle notwendigen Pakete nachgeladen und übersetzt. Die Konfigurationsdatei hat sich etwas geändert, aber die Unterschiede lassen sich schnell herausfinden. Man benötigt ca. 30 Minuten, um einen Calendar-Server zum Laufen zu bringen.