Programmieren mit Swift - Für macOS und iOS
Programmieren mit Swift - Für macOS und iOS
Eine Stoppuhr

Das nächste Projekt soll die Funktion einer Stoppuhr übernehmen. Wie Sie sehen werden, ist das mit Hilfe eines Timers sehr, leicht und die meisten der nötigen Anweisungen haben Sie auch schon in der Einführung gelernt.

Erzeugen Sie zunächst wieder ein neues Cocoa Projekt „Stoppuhr“ und fügen Sie wie gewohnt eine MyController-Klasse hinzu. Zu Anfang benötigen Sie in dieser Klasse nur eine Action und dazu eine passende Methode. Und natürlich einen NSTimer.

Die MyController.h Datei:
#import <Cocoa/Cocoa.h>
 
@interface MyController : NSObject {

    NSTimer *stopwatchTimer;

}

- (
IBAction)startstop:(id)sender;

@end
Die MyController.m Datei:
#import "MyController.h"

@implementation MyController

- (
IBAction)startstop:(id)sender
{

}

@end
Es gibt nur eine Methode, um den Timer zu starten und zu stoppen. Demzufolge gibt es auch nur einen Button, der diese Methode aufruft. Trotzdem wäre es schön, wenn sich die Beschriftung des Button ändern würde, je nach dem welche Funktion beim nächsten Anklicken aufgerufen wird.
Sicherlich ließe sich das in der Programmlogik leicht umsetzten, nötig ist es aber nicht. Schon von Hause aus ermöglicht es der Interface Builder, Schaltflächen mit wechselnden Texten zu erstellen.

Doppelklicken Sie auf die MainMenu.xib, um den Interface Builder zu starten und fügen Sie dem Programmfenster einen Button hinzu. Sehen Sie sich anschließend mit dem Inspector die Attribute des Button an.

Die Eigenschaft „Title“ kennen Sie bereits. Dies ist der Text, der im Button angezeigt wird. Gleich darunter befindet sich die Eigenschaft „Alt. Title“ für einen alternativen Text. Auch diese Eigenschaft können Sie ändern. Möchten Sie dann noch, dass sich der Text mit jedem Klick ändert, setzten Sie die Eigenschaft „Mode“ auf „Toggle“. Das ist alles!
stacks_image_704DF00A-D9F3-4390-A17B-EEA91BAD30F5
Sie können den Cocoa Simulator benutzen, um die grafische Oberfläche zu testen. Den Simulator finden Sie im Menü des Interface Builder unter File -> Simulate Interface.

Auch im Programmcode ist es sehr einfach zu ermitteln, welcher Text im Button dargestellt wird. Oder genauer gesagt, es ist einfach, den „state“ des Button zu ermitteln. Jede Action hat als Argument einen (Ab)sender, der diese Methode aufgerufen hat. In diesem Fall wird die Action durch Anklicken eines Button aufgerufen, daher ist dieses Steuerelement auch der Sender. Durch Aufruf von state kann man den aktuellen Status erfahren.

Testen können Sie dies wie so oft ganz leicht mit einem NSBeep oder NSLog. Vergessen Sie nicht, eine Klasseninstanz im Interface Builder zu erzeugen und die nötigen Verbindungen zwischen Action und Button zu erstellten.
- (IBAction)startstop:(id)sender
{
if([sender state] == 1)
    {
        NSBeep();
        NSLog(
@"Stoppuhr gestartet.");
    }
   
else
    {
        NSLog(
@"Stoppuhr angehalten.");
    }
}
Für die Implementierung der Timer Funktionen verwenden wir den gleichen Code wie im letzten Beispiel. Das Intervall ist mit einer Sekunde genau richtig.
- (IBAction)startstop:(id)sender
{
    if([sender state] == 1)
    {
        NSLog(
@"Stoppuhr gestartet.");

        stopwatchTimer = [NSTimer scheduledTimerWithTimeInterval:
1 target:self
         selector:@selector(tick:) userInfo:nil repeats:YES];

        [stopwatchTimer fire];
    }
    else
    {
        NSLog(
@"Stoppuhr angehalten.");

        [stopwatchTimer invalidate];
        stopwatchTimer =
nil;
    }
}
Die vom Timer aufzurufenden Methode erhält auch erst Mal Anweisungen, um die grundlegende Funktion der Stoppuhr zu prüfen.
- (void)tick:(NSTimer *)theTimer
{
    NSBeep();
    NSLog(
@"tick");
}
Als nächstes sollten Sie sich vor Augen halten, wie eine Stoppuhr genau funktioniert. Sie zeigt die Zeit an, die seit dem Start vergangen ist. Zwangsläufig muss sich das Programm den Startzeitpunkt merken, um später von diesem Punkt aus die verstrichene Zeit zu berechnen.

Der Datentyp NSDate ist ideal, um Datum- und Uhrzeitwerte aufzunehmen. Sendet man date an so ein Objekt, werden sogar automatisch das aktuellen Datum und die Uhrzeit gespeichert.
NSDate *startDate = [[NSDate date] retain];
Der Datentyp, um eine Zeitspanne (in Sekunden) aufzunehmen, ist NSTimeInterval. Die Anzahl der vergangenen Sekunden zwischen dem Start der Stoppuhr und der aktuellen Zeit müssen Sie nicht selber ausrechnen. Es genügt, wenn Sie „timeIntervalSinceNow“ an das NSDate Objekt senden und Sie erhalten das gewünschte Ergebnis. Da sich die Startzeit in der Vergangenheit befindet, erhalten Sie allerdings einen Wert mit einem negativen Vorzeichen. Es ist aber ein leichtes, dieses wieder umzukehren.
NSTimeInterval interval = -[startDate timeIntervalSinceNow];
Kommen wir nun zurück zur konkreten Umsetzung. In der Header-Datei brauchen Sie zwei weitere Felder. Ein NSDate, um den Startzeitpunkt der Stoppuhr zu speichern und ein IBOutlet, denn schließlich wollen Sie die gemessene Zeit auch anzeigen.
#import <Cocoa/Coding.h>

@interface MyController : NSObject {

    NSTimer *stopwatchTimer;
    NSDate * startDate;
   
IBOutlet id elapsedTime;
}

- (
IBAction)startstop:(id)sender;

@end
Das NSDate muss in der Klasse definiert werden, da beide Methoden auf dieses Objekt zugreifen müssen. In startstop wird der Wert zurückgesetzt, während in tick die verstrichene Zeit errechnet werden muss.

In der Code-Datei fehlen nur noch wenige Zeilen bis zur Fertigstellung. Mit Hilfe des Modulo-Befehles (%) können Sie das Intervall in Stunden, Minuten und Sekunden zerlegen.

Senden Sie diesen Text an Ihr Outlet und die Stoppuhr ist fertig programmiert.
- (IBAction)startstop:(id)sender
{
    if([sender state] == 1)
    {
        NSLog(
@"Stoppuhr gestartet.");

        stopwatchTimer = [NSTimer scheduledTimerWithTimeInterval:
1 target:self
         selector:
@selector(tick:) userInfo:nil repeats:YES];

        startDate = [[NSDate date] retain];

        [stopwatchTimer fire];
    }
    else
    {
        NSLog(@"Stoppuhr angehalten.");

        [stopwatchTimer invalidate];
        stopwatchTimer = nil;
        [startDate release];
    }
}
- (void)tick:(NSTimer *)theTimer
{
    NSTimeInterval interval = -[startDate timeIntervalSinceNow];
    int seconds = ((int) interval) % 60;
    int minutes = ((int) (interval - seconds) / 60) % 60;
    int hours = ((int) interval - seconds - 60 * minutes) % 3600;

    [elapsedTime setStringValue:[NSString stringWithFormat:@"%.1d:%.2d:%.2d", hours,
     minutes, seconds]];
}
Für die Darstellung der Zeit verwenden Sie ein NSTextField (Label) im Interface Builder. Ziehen Sie eine Verbindung vom elapsedTime-Outlet und testen Sie das fertige Programm ein weiteres Mal.
stacks_image_5D9B5D49-76C7-443E-928F-BDE1A26031E6