Programmieren mit Swift - Für macOS und iOS
Programmieren mit Swift - Für macOS und iOS
Der Code

Erzeugen Sie ein neues Projekt, das Sie Entfernungsrechner nennen. Die ersten Schritte sind sicherlich inzwischen Routine. Schon in der Klassendefinition des Controllers können Sie erkennen, welche Steuerelemente später im Interface Builder zum Einsatz kommen.
Zusätzlich benötigen Sie ein NSMuteableArray, welches als eine Liste von Städten dient. Oder präziser ausgedrückt, als eine Liste von Objekten der Klasse „City“.
#import <Cocoa/Cocoa.h>

@interface MyController : NSObject {

   
IBOutlet NSComboBox *comboCity1;
    IBOutlet NSComboBox *comboCity2;
    IBOutlet NSTextField *distance;

    NSMutableArray *cityArray;

}

- (
IBAction)calculateDistance:(id)sender;

@end
Ein Array ist, einfach ausgedrückt, nur eine Auflistung beliebiger Objekte und wird in Objective-C durch den Typ NSArray umgesetzten. NSMuteableArry ist eine Erweiterung dieser Klasse und ermöglicht das Hinzufügen weiterer Objekte nach der Initialisierung. Bei einem NSArray müsste man schon zu Anfang alle Objekte übergeben. Das wäre für dieses Programm zwar auch möglich, ist aber vom Programmcode her weniger übersichtlich.
In diesem Beispiel soll ein NSMuteableArry eine Liste von Objekten der Klasse City verwalten. Diese Klasse müssen Sie aber erst erstellen. Fügen Sie also eine weitere Klasse Ihrem Projekt hinzu, genau so, wie Sie es sonst für die Controller-Klasse tun. Statt Accessor-Methoden können Sie aber dieses Mal Properties verwenden.

Die City.h Datei
#import <Cocoa/Cocoa.h>

@interface City : NSObject {

    NSString *cityName;
    float longitude;
    float latitude;
}

@property float longitude;
@property float latitude;
@property (readwrite,copy) NSString *cityName;

@end
Die City.m Datei
#import "City.h"

@implementation City

@synthesize longitude;
@synthesize latitude;
@synthesize cityName;

- (
id) initWithValues:(NSString *)aName:(float)aLongitude:(float)aLatitude
{
    [
super init];
    aName = [aName copy];
    [cityName release];
    cityName = aName;

    longitude = aLongitude;
    latitude = aLatitude;

    return self;
}

- (
void) dealloc
{
    [cityName dealloc];
    [
super dealloc];
}

@end
Diese Klasse tut nicht besonders viel, sie soll nur als Datencontainer dienen. Legen Sie besondere Aufmerksamkeit auf die initWithValues-Methode. Diese vereinigte die Funktionalität dreier einzelner Methoden. Dadurch wird es möglich, beim Initialisieren eines City-Objektes sofort alle Daten der Stadt mit zu übergeben. So wird der folgende Programmcode etwas übersichtlicher.

Der nächste Schritt ist, das Array dieser Klassen zu initialisieren. Dies geschieht am besten bei der Initialisierung der Controller-Klasse. Wenn man diese Methode überschreibt, darf man aber die Initialisierung der Superklasse nicht vergessen.
- (id) init
{
    if( self = [super init])
    {
        cityArray = [[NSMutableArray alloc] init];
    }
    return self;
}
Das Array von Städten und Koordinaten füllen Sie in der awakeFromNib-Methode Ihrer Controller-Klasse, denn dort können Sie auch gleich die Listen den ComboBoxen zuweisen. Natürlich müssen Sie sich nicht auf die Städte aus diesem Beispielcode beschränken. Mit dem Geo Converter können Sie für beliebige weitere Städte die Koordinaten errechnen. Verwenden Sie die Gradwerte für die Koordinaten. Vor der Berechnung wird das Programm diese Werte in die benötigten Bogenmaßwerte umrechnen.
-(void)awakeFromNib
{
    City *aCity =
    [[City alloc] initWithValues:@"Harsewinke" :8.21463078946776 :51.9718664945271];
    [cityArray addObject:aCity];
    [aCity release];

    aCity =
    [[City alloc] initWithValues:@"Schwennigen" :10.6535348271871 :48.6457679042561];
    [cityArray addObject:aCity];
    [aCity release];

    aCity =
    [[City alloc] initWithValues:@"Carlsberg":8.03911829128339: 49.4983469530696];
    [cityArray addObject:aCity];
    [aCity release];

    for(int i = 0 ; i < [cityArray count] ; i ++)
    {
        City *city = [cityArray objectAtIndex:i];
        [comboCity1 addItemWithObjectValue: [city cityName]];
        [comboCity2 addItemWithObjectValue: [city cityName]];
    }

    [comboCity1 selectItemAtIndex:0];
    [comboCity2 selectItemAtIndex:0];

    [comboCity1 setEditable:NO];
    [comboCity2 setEditable:NO];
}
Die NSComboBox funktioniert ähnlich wie ein NSTextField, das Sie schon kennengelernt haben. Auch bei diesem Steuerelement werden die Werte und Einstellungen über Nachrichten an das Objekt gesendet. Die wichtigste Funktion einer ComboBox ist natürlich die Liste von Elementen, aus der ein Benutzer wählen kann. Die Aufgabe des Entwicklers ist es, Objekte zu dieser Liste hinzuzufügen. Das kann auf verschiedene Arten realisiert werden, in diesem Fall mit der Nachricht addItemWithObjectValue.
Mit selectItemAtIndex wird der erste Eintrag dieser Liste ausgewählt und setEditable:NO verhindert, dass der Benutzer selbst einen neuen Text in die ComboBox eintragen kann. Das kann in machen Fällen wünschenswert sein, in diesem Fall aber nicht.

Was nun noch fehlt, ist die calculateDistance-Methoden, die ja schon in der Headerdatei des Controllers angekündigt wurde. Doch bevor man wirklich rechen kann, müssen einige andere Dinge erledigt werden.

Die Koordinaten für die vom Benutzer ausgewählten Städte zu ermitteln ist denkbar einfach, denn glücklicherweise entspricht die Position des gewählten Eintrages in der Combobox auch der Position der Stadt im Array.
City *city1 =
     [cityArray objectAtIndex: [comboCity1 indexOfSelectedItem] ];

City *city2 =
     [cityArray objectAtIndex: [comboCity2 indexOfSelectedItem] ];
Bei diesen Anweisungen werden verschachtelte Methoden (englisch nested methods) verwendet. Das bedeutet, der Rückgabewert einer Methode dient sofort als Parameter für eine andere Methode, ohne dass eine seperate Anweisung geschrieben werden muss. Dabei werden die Methoden von rechts nach links, oder falls Klammern verwendet werden, von innen nach aussen, abgearbeitet.

In diesem konkreten Fall bedeutet es, dass [comboCity1 indexOfSelectedItem] aufgerufen wird, eine Methode, die die Indexposition des ausgewählten ComboBox Objektes zurück gibt. Dieser Wert dient dann als Parameter für objectAtIndex, welcher das Objekt im Array an der angegebenen Position liefert. Das ist natürlich ein Objekt vom Typ City, denn genau aus diesen Typen besteht das Array. Hat man die beiden Städte, kann gerechnet werden.

Wie man Entfernungen auf einer Kugeloberfäche berechnet, lassen Sie sich am besten von einem Mathematiker erklären, dabei darf man nämlich solche Dinge wie den Erdradius nicht außer Acht lassen. Im Internet gibt es zum Glück genug Webseiten, die einen mit den richtigen Formeln versorgen. Zuerst gilt, es die Koordinaten der Auswahl in das Bogenmaß umzurechnen.
float city1latitudeArc = [city1 latitude] / 180 * M_PI;
float city1longitudeArc = [city1 longitude] / 180 * M_PI;

float city2latitudeArc = [city2 latitude] / 180 * M_PI;
float city2longitudeArc = [city2 longitude] / 180 * M_PI;
Mit der richtigen Formel geht es dann weiter, und am Ende muss nur noch das Resultat an ein NSTextField gesendet werden. Vorher wird das Ergebnis noch in ein NSString umgewandelt. Das ist nicht unbedingt nötig, aber auf diesem Wege kann man bequem die Nachkommastellen reduzieren. Im Programmcode sieht das dann so aus:
float earthRadius = 6378.137;

double dist = acos(sin(city1latitudeArc) * sin(city2latitudeArc) +
     cos(city1latitudeArc) * cos(city2latitudeArc) *
     cos(city1longitudeArc - city2longitudeArc)) * earthRadius;

NSString *distancestring = [NSString stringWithFormat:
@"%.2f", dist];
[distance setStringValue:distancestring];
Zum Schluss noch einmal die komplette Methode im Überblick. Auf der nächsten Seite erfahren Sie dann, wie Sie für die Anwendung die grafische Oberfläche entwerfen.
- (IBAction)calculateDistance:(id)sender
{
    City *city1 = [cityArray objectAtIndex: [comboCity1 indexOfSelectedItem] ];

    City *city2 = [cityArray objectAtIndex: [comboCity2 indexOfSelectedItem] ];

    float city1latitudeArc = [city1 latitude] / 180 * M_PI;
    float city1longitudeArc = [city1 longitude] / 180 * M_PI;

    float city2latitudeArc = [city2 latitude] / 180 * M_PI;
    float city2longitudeArc = [city2 longitude] / 180 * M_PI;

    float earthRadius = 6378.137;

    double dist = acos(sin(city1latitudeArc) * sin(city2latitudeArc) +
     cos(city1latitudeArc) * cos(city2latitudeArc) *
     cos(city1longitudeArc - city2longitudeArc)) * earthRadius;

    NSString *distancestring = [NSString stringWithFormat:
@"%.2f", dist];
    [distance setStringValue:distancestring];
}