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

Obwohl die Anwendung, die Sie bisher programmiert haben, problemlos funktioniert, hat sie noch Schönheitsfehler. So ist es durchaus möglich, auch Buchstaben in die Eingabefelder einzutragen. Zwar kann das Programm damit nicht zum Abstürzen gebracht werden, aber schön ist dieses Verhalten nicht. Zum Glück haben die Entwickler von Apple hier schon mitgedacht und für solche Zwecke eine passende Klasse parat. Den NSFormatter!
Bevor Sie aber so eine Klasse in Ihren GeoConverter einbauen, ist es angebracht, sich etwas näher mit dem NSFormatter zu beschäftigen. Erzeugen Sie zu diesem Zweck ein neues Cocoa Projekt welches Sie „FormatterTest“ nennen.
Erstellen Sie zuerst wieder eine Klasse MyController, die als Steuerung für Ihre Fenster dient. In dieser Klasse benötigen Sie ein Outlet vom Typ NSTextField. Fügen Sie es in der MyController.h Datei hinzu und nennen es: myText.
#import <Cocoa/Cocoa.h>

@interface MyController : NSObject {

    IBOutlet NSTextField *myText;
}

@end
Erstellen Sie nun das Programmfenster im Interface Builder. Auch hier benötigen Sie nur ein einzelnes NSTextField. Erzeugen Sie anschließend eine Instanz der Klasse MyController, indem Sie wie gewohnt ein NSObject aus der Steuerelement-Bibliothek ziehen. Als letzten Schritt verbinden Sie noch Outlet und NSTextField.
stacks_image_3409443D-E84D-46AE-A49C-F8E2C70D238D
Zwar finden Sie in der Bibliothek schon Klassen, die auf NSFormatter basieren, wie den NSDateFormatter oder den NSNumberFormatter, aber für dieses Beispiel werden Sie eine eigene Klasse erstellen. Um einen eignen NSFormatter zu erstellen, müssen Sie ihn hierfür von einer bestehenden Klasse ableiten.

Zu diesem Zweck erzeugen Sie eine weitere Objective-C Klasse für Ihr Projekt. Nennen Sie diese MyFormatter und ändern Sie die Basisklasse, sodass die Klasse von NSFormatter erbt.
#import <Cocoa/Cocoa.h>

@interface MyFormatter : NSFormatter {

}

@end
Ein NSFormatter ist ein sehr mächtiges Werkzeug, das sehr viele verschiedene Anwendungsmöglichkeiten unterstützt. Zwei Methoden, die ein Formatter aber immer benötigt, sind stringForObjectValue und getObjectValue. Diese Methoden sorgen dafür, dass Werte in den Formatter hinein und wieder hinaus kommen. Ausserdem kann der Formatter die eingegebenen Werte überprüfen. Um dies zu erreichen, müssen Sie auch die Methode isPartialStringValid implementieren. Wann ein eingegebener Text gültig, also valid, ist, können Sie dabei selbst festlegen.

Verändern Sie Ihre MyFormatter.h Datei wie folgt, um die drei Methoden zu deffinieren. Instanzvariablen hat dieses Klasse keine.
#import <Cocoa/Cocoa.h>

@interface MyFormatter : NSFormatter {

}

- (NSString*)stringForObjectValue:(
id)obj;

- (
BOOL)getObjectValue:(id*)obj forString:(NSString*)string errorDescription:
     (NSString**)error;

- (
bool)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)
     newString errorDescription:(NSString**)error;

@end
Anschliessend bearbeiten Sie die MyFormatter.m Datei, bis Sie so aussieht:
#import "MyFormatter.h"

@implementation MyFormatter

- (NSString*)stringForObjectValue:(
id)obj
{
    return [obj description];
}

- (
BOOL)getObjectValue:(id*)obj forString:(NSString*)string errorDescription:
     (NSString**)error
{
    *obj = [[string copy] autorelease];
    return YES;
}

@end
Dies sind die beiden schon angesprochenen Methoden zur Daten-Ein- und -Ausgabe, die aber im Moment weniger interessant sind. Was noch fehlt, ist die Implementierung der isPartialStringValid-Methode. Fügen Sie auch diese, zunächst leere Methode hinzu.
-(bool)isPartialStringValid:(NSString*)partialString
        newEditingString:(NSString**) newString
        errorDescription:(NSString**)error
{
    return YES;
}
Die Methode gibt den boolschen Wert YES zurück, wenn der übergebene String gültig ist und in diesem Fall in ein NSTextField oder anderes Steuerelement geschrieben werden darf. Diese Methode ist die Stelle, an der Sie Manipulationen vornehmen können. Wollen Sie beispielsweise, dass der eingegebene Text automatisch in Großbuchstaben konvertiert wird, sähe die Methode so aus:
-(bool)isPartialStringValid:(NSString*)partialString
        newEditingString:(NSString**) newString
        errorDescription:(NSString**)error
{
    *newString = [partialString uppercaseString];
    return NO;
}
Hier ist partialString der in die Methode übergebene Text und newString ist der Text, der verwendet werden soll, wenn der Rückgabewert der Methode NO ist. In diesem Beispiel also eine in Großbuchstaben umgewandelte Version des ursprünglichen Textes. Ebenfalls ein NSString ist errorDescription. Dieser Text kann verwendet werden, um dem Anwender mitzuteilen, warum seine Eingabe nicht gültig war.

Um Ihren eigenen NSFormatter zu verwenden, ist es nötig, ihn einem Steuerelement zuzuweisen. Für das Testprogramm geschieht dies am besten in der awakeFromNib Methode in der Controller Klasse. Vergessen Sie dabei nicht die import Anweisung für die Formatter-Klasse!
#import "MyController.h"
#import
"MyFormatter.h"

@implementation MyController

- (
void)awakeFromNib
{
    MyFormatter *aFormatter = [[MyFormatter alloc]init] ;
    [myText setFormatter:aFormatter];
    [aFormatter release];
}

@end
Wenn Sie jetzt Ihre Anwendung testen, werden Sie feststellen, dass nur Großbuchstaben im Textfeld erscheinen, ganz egal, welche Buchstaben Sie eingeben. Der Formatter funktioniert soweit also gut. Allerdings war die gesetzte Aufgabenstellung eine andere: Sie wollten die Eingabe von Buchstaben komplett unterdrücken. Folgerichtig sollte man sich den eingegebenen Text mal etwas näher ansehen.

Verwenden Sie die NSLog Methode, um den partialString in der Console auszugeben.
-(bool)isPartialStringValid:(NSString*)partialString
        newEditingString:(NSString**) newString
        errorDescription:(NSString**)error
{
    *newString = [partialString uppercaseString];
    NSLog(
@"%@",partialString);
    return NO;
}
stacks_image_B0302812-A81E-4EF2-A565-07A2FF9EEECE
Wie Sie sehen, enthält partialString immer den aktuell im Textfeld enthaltenen Text, plus das zuletzt eingegebene Zeichen. Man erhält also einen recht guten Überblick, was in dem Textfeld steht und was eingegeben wurde. Wie aber kann man sicherstellen, dass nur Zahlen eingegeben werden können? Es wäre sicher notwendig, partialString irgendwie zu vergleichen, aber das gestaltet sich überaus schwierig. Gibt man zum Beispiel die Zahlenfolge 1,2 und 3 ein, so ist partialString beim ersten Aufruf 1, beim zweiten Aufruf 12 und beim letzten Aufruf 123. Dies mit if oder switch Anweisungen zu vergleichen ist nahezu unmöglich. Viel sinnvoller wäre es zu ermitteln, ob das eingegebene Zeichen eine Zahl ist, oder genauer, ob es sich in der Gruppe der Zeichen befindet. die Zahlen repräsentieren.

Eine Gruppe von (Unicode) Zeichen werden in Objective-C durch NSCharacterSet abgebildet. Erzeugen Sie als ersten Schritt in der isPartialStringValid-Methode ein NSCharacterSet. welches alle unzulässigen Zeichen enthält:
NSCharacterSet *nonNumbers;
nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];

Ein NSCharacterSet kann auf viele Arten erstellt werden. Hier kommt decimalDigitCharacterSet zum Einsatz. Es gibt aber noch zahlreiche weitere Möglichkeiten, so können Sie zum Beispiel ein Set nur aus Großbuchstaben erzeugen. Der Befehl invertedSet kehrz die ganze Auswahl um, sodass Sie hier ein Set bekommen, das eben keine Zahlen enthält.

Will man nun den partialString mit dem Set der nicht zulässigen Zeichen vergleichen, muss man dies nicht für jedes Zeichen einzeln tun. rangeOfCharacterFromSet erledigt das für Sie.
-(bool)isPartialStringValid:(NSString*)partialString
     newEditingString:(
NSString**) newString
     errorDescription:(
NSString**)error
{
    NSCharacterSet *nonNumbers;
     nonNumbers = [[
NSCharacterSet decimalDigitCharacterSet] invertedSet];

    if ([partialString rangeOfCharacterFromSet: nonNumbers options:
     NSLiteralSearch
].location != NSNotFound)
     {
        
return NO;
     }
    
else
     {
        
return YES;
     }
}
Hierbei bewirkt NSLiteralSearch einen Byte für Byte Vergleich und location ergibt die Position, an der eine Übereinstimmung gefunden wurde. Oder eben NSNotFound, wenn es keine Übereinstimmung gibt. Wie Sie sicher gemerkt haben, wird im Falle des Rückgabewertes NO, kein neuer alternativer Text übergeben. Es soll ja auch im Falle eines ungültigen Zeichens kein Text ausgegeben werden.

Testen Sie das Programm! Wenn alles zu Ihrer Zufriedenheit funktioniert, schliessen Sie dieses Projekt und laden Sie anschliessend wieder Ihr GeoConverter Projekt.

nächste Seite