Programmieren mit Swift - Für macOS und iOS
Programmieren mit Swift - Für macOS und iOS
Binding, Teil 1

In der rollCharacterAttributes-Methode geschehen jetzt zwei Dinge. Zum einen werden für den Character neue Eigenschaften ermittelt. Das ist die eigentliche Aufgabe der Methode und muss so sein. Ausserdem gibt es aber einen Anweisungsblock, der nicht minder lang ist und dafür sorgt, dass die Werte auch angezeigt werden. Diese Art von Anweisungen bezeichnet man auch als Glue-Code. Glue, aus dem Englischen von „Kleber“ - weil diese Befehle nichts anders tun, als die ermittelten Werte an die Oberfläche anzukleben. Es sind Anweisungen ohne große Herausforderung, aber sind eben nötig, damit der Benutzer auch etwas sieht.

Glue-Code zu schreiben ist für einen Entwickler meistens langweilig und zu allem Überfluss können diese Anweisungen selten in anderen Anwendungen wiederverwendet werden, ganz im Gegensatz zur Klasse Dice oder Character. Noch schlimmer wird es, wenn Sie eine zweite Methode haben, die ebenfalls Eigenschaften des Characters verändert, zum Beispiel, wenn die Werte geladen werden. Auch dann benötigen Sie wieder Anweisungen, welche die aktuellen Werte auf die grafische Oberfläche bringen. Zwar könnten Sie diese Anweisung in einer Methode auslagern, aber besonders praktisch ist das auch nicht. Besser wäre es, die Eigenschaften der Klasse Character könnten permanent mit den Textfeldern verbunden werden und immer wenn sich eine Eigenschaft ändert, wird das Textfeld automatisch aktualisiert. Dank Cocoa Binding ist das tatsächlich möglich.
Durch Binding wird eine Eigenschaft des View, wie ein Wert in einem Textfeld, mit einer Eigenschaft im Datenmodel verbunden und synchronisiert. Dieses Programm hat kein komplexes Datenmodel - es ist das Objekt rpgCharater, welches diese Aufgabe komplett übernimmt. Aber auch hier funktioniert schon das Binding.

Mit geringem Aufwand können Binding-Verbindungen im Objective-C Code realisiert werden. Die Syntax ist dabei recht einfach und gut nachzuvollziehen.
[txtStrength bind:@"value" toObject:rpgCharacter withKeyPath:@"strength.intValue"
     options:
nil];
Diese Anweisung macht nichts anders, als das Textfeld txtStrength an das Model rpgCharacter zu binden. Und dort an die Eigenschaft strength als intValue.

Wie man ganz deutlich sehen kann, wird auch hier wieder Key-Value-Coding verwendet, denn alle Eigenschaften und Instanzvariablen werden als Zeichenkette übergeben. Bedeutet das, man könnte auch Variablen verwenden? Ja, genau so ist es.
NSString *path = @"strength.intValue";

[txtStrength bind:
@"value" toObject:rpgCharacter withKeyPath:path options:nil];
Diese Art der Programmierung ist natürlich in dieser Anwendung nicht nötig, aber wenn Sie die Parameter dynamisch festlegen wollten, könnten Sie es so tun.

Ein weiterer bisher unbenutzter Parameter bei dieser Anweisung ist options. Dahinter verbergen sich weitere Optionen, die für diese Verbindung festgelegt werden können. Die Bindingoptions werden durch ein NSDictionary realisiert. Im folgenden Beispiel wird für eine Verbindung ein NSNullPlaceholder bestimmt. Also ein Platzhalter, der immer dann angezeigt wird, wenn das angebundene Feld Null ist. Bei dem Objekt rpgCharater ist das der Fall, wenn noch keine Werte ausgewürfelt wurden. Dann soll n/a für not available angezeigt werden.
NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
[bindingOptions setObject:
@"n/a" forKey:@"NSNullPlaceholder"];

[txtStrength bind:
@"value" toObject:rpgCharacter withKeyPath:@"strength.intValue"
     options:bindingOptions];
stacks_image_2078D748-A8A6-489D-994C-A4ABAEBE256F
Jetzt gilt es nur noch alle Textfelder auf Binding umzustellen. Dann hat die rollCharacterAttributes-Methode auch nur noch die Aufgabe, neue Werte zu ermitteln.
-(void) awakeFromNib
{
    rpgDice = [[Dice alloc] init];
    rpgCharacter = [[Character alloc] init];

    NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
    [bindingOptions setObject:@"n/a" forKey:@"NSNullPlaceholder"];

    [txtStrength bind:@"value" toObject:rpgCharacter withKeyPath:@"strength.intValue"
     options:bindingOptions];

    [txtDexterity bind:@"value" toObject:rpgCharacter
 
    withKeyPath:@"dexterity.intValue" options:bindingOptions];

[txtConstitution bind:
@"value" toObject:rpgCharacter
 
    withKeyPath:@"constitution.intValue" options:bindingOptions];

[txtIntelligence bind:
@"value" toObject:rpgCharacter
 
    withKeyPath:@"intelligence.intValue" options:bindingOptions];

[txtWisdom bind:
@"value" toObject:rpgCharacter withKeyPath:@"wisdom.intValue"
     options:bindingOptions];

[txtCharisma bind:
@"value" toObject:rpgCharacter withKeyPath:@"charisma.intValue"
     options:bindingOptions];
}

- (
IBAction)rollCharacterAttributes:(id)sender
{
    [rpgCharacter setStrength: [rpgDice Roll3W6]];
    [rpgCharacter setDexterity: [rpgDice Roll3W6]];
    [rpgCharacter setConstitution: [rpgDice Roll3W6]];
    [rpgCharacter setIntelligence: [rpgDice Roll3W6]];
    [rpgCharacter setWisdom: [rpgDice Roll3W6]];
    [rpgCharacter setCharisma: [rpgDice Roll3W6]];
}