1. Code
  2. Mobile Development
  3. iOS Development

Erstellen einer Wetter-App mit Vorhersage - API-Integration

Scroll to top

German (Deutsch) translation by Katharina Grigorovich-Nevolina (you can also view the original English article)

Im ersten Artikel dieser Reihe haben wir den Grundstein für das Projekt gelegt, indem wir das Projekt eingerichtet und die Struktur der Applikation erstellt haben. In diesem Artikel nutzen wir die AFNetworking-Bibliothek, um mit der Vorhersage-API zu interagieren.


Einführung

Im ersten Teil dieser Serie haben wir den Grundstein für unsere Wetter-Applikation gelegt. Benutzer können ihren aktuellen Standort hinzufügen und zwischen Standorten wechseln. In diesem Tutorial verwenden wir die AFNetworking-Bibliothek, um die Vorhersage-API nach den Wetterdaten des aktuell ausgewählten Standorts zu fragen.

Wenn Sie mitmachen möchten, benötigen Sie einen Vorhersage-API-Schlüssel. Sie können einen API-Schlüssel erhalten, indem Sie sich bei Vorhersage als Entwickler registrieren. Die Registrierung ist kostenlos, daher empfehle ich Ihnen, den Wettervorhersagedienst auszuprobieren. Sie finden Ihren API-Schlüssel unten in Ihrem Dashboard (Abbildung 1).

Create a Weather App with Forecast – Forecast Integration - Obtaining Your API Key Create a Weather App with Forecast – Forecast Integration - Obtaining Your API Key Create a Weather App with Forecast – Forecast Integration - Obtaining Your API Key
Abbildung 1: Abrufen Ihres API-Schlüssels

1. Unterklasse AFHTTPClient

Wie ich bereits in diesem Artikel geschrieben habe, verwenden wir die AFNetworking-Bibliothek für die Kommunikation mit der Vorhersage-API. Bei der Arbeit mit AFNetworking gibt es mehrere Optionen. Um unsere Applikation jedoch zukunftssicher zu machen, entscheiden wir uns für die AFHTTPClient-Klasse. Diese Klasse wurde für die Nutzung von Webdiensten wie der Vorhersage-API entwickelt. Obwohl wir nur auf einen API-Endpunkt zugreifen, ist es dennoch nützlich, den AFHTTPClient zu verwenden, wie Sie in wenigen Augenblicken erfahren werden.

Es wird empfohlen, für jeden Webdienst eine AFHTTPClient-Unterklasse zu erstellen. Da wir AFNetworking bereits im vorherigen Tutorial zu unserem Projekt hinzugefügt haben, können wir sofort mit der Unterklasse von AFHTTPClient beginnen.

Schritt 1: Erstellen Sie die Klasse

Erstellen Sie eine neue Objective-C-Klasse, nennen Sie sie MTForecastClient und machen Sie sie zu einer Unterklasse von AFHTTPClient (Abbildung 2).

Create a Weather App with Forecast – Forecast Integration - Subclassing AFHTTPClient Create a Weather App with Forecast – Forecast Integration - Subclassing AFHTTPClient Create a Weather App with Forecast – Forecast Integration - Subclassing AFHTTPClient
Abbildung 2: Unterklasse AFHTTPClient

Schritt 2: Erstellen eines Singleton-Objekts

Wir werden das Singleton-Muster übernehmen, um die Verwendung der MTForecastClient-Klasse in unserem Projekt zu vereinfachen. Dies bedeutet, dass während der gesamten Lebensdauer der Applikation jeweils nur eine Instanz der Klasse aktiv ist. Möglicherweise sind Sie bereits mit Singleton-Mustern vertraut, da diese in vielen objektorientierten Programmiersprachen häufig vorkommen. Auf den ersten Blick scheint das Singleton-Muster sehr praktisch zu sein, aber es gibt eine Reihe von Einschränkungen, auf die Sie achten müssen. Sie können mehr über Singletons erfahren, indem Sie diesen ausgezeichneten Artikel von Matt Gallagher lesen.

Das Erstellen eines Singleton-Objekts ist in Objective-C ziemlich einfach. Deklarieren Sie zunächst eine Klassenmethode in MTForecastClient.h, um den öffentlichen Zugriff auf das Singleton-Objekt zu ermöglichen (sehen Sie unten).

1
#import "AFHTTPClient.h"

2
3
@interface MTForecastClient : AFHTTPClient
4
5
#pragma mark -

6
#pragma mark Shared Client

7
+ (MTForecastClient *)sharedClient;
8
9
@end

Die Implementierung von sharedClient mag zunächst entmutigend aussehen, aber es ist nicht so schwierig, wenn Sie erst einmal verstanden haben, was los ist. Wir deklarieren zunächst zwei statische Variablen,(1) predicate vom Typ dispatch_once_t und (2) _sharedClient vom Typ MTForecastClient. Wie der Name schon sagt, ist predicate ein Prädikat, das wir in Kombination mit der Funktion dispatch_once verwenden. Wenn Sie mit einer Variablen vom Typ dispatch_once_t arbeiten, ist es wichtig, dass sie statisch deklariert wird. Die zweite Variable, _sharedClient, speichert einen Verweis auf das Singleton-Objekt.

Die Funktion dispatch_once nimmt einen Zeiger auf eine Struktur dispatch_once_t, das Prädikat und einen Block. Das Schöne an dispatch_once ist, dass der Block einmal während der gesamten Lebensdauer der Applikation ausgeführt wird. Genau das möchten wir. Die Funktion dispatch_once hat nicht viele Verwendungszwecke, aber dies ist definitiv eine davon. In dem Block, den wir an dispatch_once übergeben, erstellen wir das Singleton-Objekt und speichern eine Referenz in _sharedClient. Es ist sicherer, alloc und init separat aufzurufen, um eine Racebedingung zu vermeiden, die möglicherweise zu einem Deadlock führen kann. Euh ... was? Weitere Informationen zu den wichtigsten Details zu Stack Overflow finden Sie hier.

1
+ (MTForecastClient *)sharedClient {
2
    static dispatch_once_t predicate;
3
    static MTForecastClient *_sharedClient = nil;
4
5
    dispatch_once(&predicate, ^{
6
        _sharedClient = [self alloc];
7
        _sharedClient = [_sharedClient initWithBaseURL:[self baseURL]];
8
    });
9
10
    return _sharedClient;
11
}

Bei der Implementierung der sharedClient-Klassenmethode ist es wichtig zu verstehen, dass der Initialisierer initWithBaseURL: nur einmal aufgerufen wird. Das Singleton-Objekt wird in der statischen Variablen _sharedClient gespeichert, die von der Klassenmethode sharedClient zurückgegeben wird.

Schritt 3: Konfigurieren des Clients

In sharedClient rufen wir initWithBaseURL: auf, das wiederum baseURL, eine andere Klassenmethode, aufruft. In initWithBaseURL: legen wir einen Standardheader fest. Dies bedeutet, dass der Client diesen Header zu jeder von ihm gesendeten Anforderung hinzufügt. Dies ist einer der Vorteile der Arbeit mit der AFHTTPClient-Klasse. In initWithBaseURL: registrieren wir auch eine HTTP-Operationsklasse, indem wir registerHTTPOperationClass: aufrufen. Die AFNetworking-Bibliothek bietet eine Reihe spezialisierter Operationsklassen. Eine dieser Klassen ist die AFJSONRequestOperation-Klasse, die die Interaktion mit einer JSON-API sehr einfach macht. Da die Vorhersage-API eine JSON-Antwort zurückgibt, ist die AFJSONRequestOperation-Klasse eine gute Wahl. Die Methode registerHTTPOperationClass: funktioniert ähnlich wie die Methode registerClass: forCellReuseIdentifier: der Klasse UITableView. Indem Sie dem Client mitteilen, welche Operationsklasse wir für die Interaktion mit dem Webdienst verwenden möchten, werden Instanzen dieser Klasse für uns unter der Haube instanziiert. Warum dies nützlich ist, wird in wenigen Augenblicken klar.

1
- (id)initWithBaseURL:(NSURL *)url {
2
    self = [super initWithBaseURL:url];
3
4
    if (self) {
5
        // Accept HTTP Header

6
        [self setDefaultHeader:@"Accept" value:@"application/json"];
7
8
        // Register HTTP Operation Class

9
        [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
10
    }
11
12
    return self;
13
}

Die Implementierung von baseURL ist nichts anderes als eine bequeme Methode zum Erstellen der Basis-URL des Clients. Die Basis-URL ist die URL, über die der Client den Webdienst erreicht. Dies ist die URL ohne Methodennamen oder Parameter. Die Basis-URL für die Vorhersage-API lautet https://api.forecast.io/forecast//.. Wie Sie sehen, ist der API-Schlüssel Teil der URL. Dies mag unsicher erscheinen und ist es tatsächlich. Es ist nicht schwierig für jemanden, den API-Schlüssel abzurufen. Daher ist es ratsam, mit einem Proxy zu arbeiten, um den API-Schlüssel zu maskieren. Da dieser Ansatz etwas komplizierter ist, werde ich diesen Aspekt in dieser Serie nicht behandeln.

1
+ (NSURL *)baseURL {
2
    return [NSURL URLWithString:[NSString stringWithFormat:@"https://api.forecast.io/forecast/%@/", MTForecastAPIKey]];
3
}

Möglicherweise haben Sie bei der Implementierung von baseURL festgestellt, dass ich eine andere Zeichenfolgenkonstante zum Speichern des API-Schlüssels verwendet habe. Dies scheint unnötig zu sein, da wir den API-Schlüssel nur an einem Ort verwenden. Es wird jedoch empfohlen, Applikationsdaten an einem Ort oder in einer Eigenschaftsliste zu speichern.

1
#pragma mark -

2
#pragma mark Forecast API

3
extern NSString * const MTForecastAPIKey;
1
#pragma mark -

2
#pragma mark Forecast API

3
NSString * const MTForecastAPIKey = @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

Schritt 4: Hinzufügen einer Hilfsmethode

Bevor wir fortfahren, möchte ich die MTForecastClient-Klasse um eine Hilfs- oder Komfortmethode erweitern, die das Abfragen der Vorhersage-API erleichtert. Diese bequeme Methode akzeptiert einen Ort und einen Abschlussblock. Der Abschlussblock wird ausgeführt, wenn die Anforderung beendet ist. Um die Arbeit mit Blöcken zu vereinfachen, wird empfohlen, einen benutzerdefinierten Blocktyp wie unten gezeigt zu deklarieren. Wenn Sie sich mit Blöcken immer noch unwohl fühlen, empfehle ich Ihnen, diesen großartigen Artikel von Akiel Khan zu lesen.

Der Block akzeptiert zwei Argumente:(1) einen Booleschen Wert, der angibt, ob die Abfrage erfolgreich war, und(2) ein Wörterbuch mit der Antwort von der Abfrage. Die Convenience-Methode requestWeatherForCoordinate:completion: übernimmt die Koordinaten eines Standorts (CLLocationCoordinate2D) und eines Abschlussblocks. Durch die Verwendung eines Abschlussblocks können wir vermeiden, ein benutzerdefiniertes Delegatenprotokoll zu erstellen, oder auf die Verwendung von Benachrichtigungen zurückgreifen. Blöcke passen perfekt zu dieser Art von Szenario.

1
#import "AFHTTPClient.h"

2
3
typedef void (^MTForecastClientCompletionBlock)(BOOL success, NSDictionary *response);
4
5
@interface MTForecastClient : AFHTTPClient
6
7
#pragma mark -

8
#pragma mark Shared Client

9
+ (MTForecastClient *)sharedClient;
10
11
#pragma mark -

12
#pragma mark Instance Methods

13
- (void)requestWeatherForCoordinate:(CLLocationCoordinate2D)coordinate completion:(MTForecastClientCompletionBlock)completion;
14
15
@end

In requestWeatherForCoordinate: completion: rufen wir getPath:success:failure: auf, eine in AFHTTPClient deklarierte Methode. Das erste Argument ist der Pfad, der an die zuvor erstellte Basis-URL angehängt wird. Das zweite und dritte Argument sind Blöcke, die ausgeführt werden, wenn die Anforderung erfolgreich ist bzw. fehlschlägt. Die Erfolgs- und Misserfolgsblöcke sind ziemlich einfach. Wenn ein Abschlussblock an requestWeatherForCoordinate: completion: übergeben wurde, führen wir den Block aus und übergeben einen booleschen Wert und das Antwortwörterbuch (oder nil im Fehlerblock). Im Fehlerblock protokollieren wir den Fehler vom Fehlerblock in der Konsole, um das Debuggen zu erleichtern.

1
- (void)requestWeatherForCoordinate:(CLLocationCoordinate2D)coordinate completion:(MTForecastClientCompletionBlock)completion {
2
    NSString *path = [NSString stringWithFormat:@"%f,%f", coordinate.latitude, coordinate.longitude];
3
    [self getPath:path parameters:nil success:^(AFHTTPRequestOperation *operation, id response) {
4
        if (completion) {
5
            completion(YES, response);
6
        }
7
8
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
9
        if (completion) {
10
            completion(NO, nil);
11
12
            NSLog(@"Unable to fetch weather data due to error %@ with user info %@.", error, error.userInfo);
13
        }
14
    }];
15
}

Möglicherweise fragen Sie sich, was das response-Objekt in den Erfolgsblöcken oder Referenzen ist. Obwohl die Vorhersage-API eine JSON-Antwort zurückgibt, ist das response-Objekt im Erfolgsblock eine NSDictionary-Instanz. Der Vorteil der Arbeit mit der AFJSONHTTPRequestOperation-Klasse, die wir in initWithBaseURL: registriert haben, besteht darin, dass sie die JSON-Antwort akzeptiert und automatisch ein Objekt aus den Antwortdaten erstellt, in diesem Beispiel ein Wörterbuch.


2. Abfragen der Vorhersage-API

Schritt 1: setLocation ändern:

Mit der MTForecastClient-Klasse ist es an der Zeit, die Vorhersage-API abzufragen und die Wetterdaten für den aktuell ausgewählten Standort abzurufen. Der am besten geeignete Ort hierfür ist die setLocation:-Methode der MTWeatherViewController-Klasse. Ändern Sie die setLocation:-Methode wie unten gezeigt. Wie Sie sehen, rufen wir nur fetchWeatherData auf, eine weitere Hilfsmethode.

1
- (void)setLocation:(NSDictionary *)location {
2
    if (_location != location) {
3
        _location = location;
4
5
        // Update User Defaults

6
        NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
7
        [ud setObject:location forKey:MTRainUserDefaultsLocation];
8
        [ud synchronize];
9
10
        // Post Notification

11
        NSNotification *notification1 = [NSNotification notificationWithName:MTRainLocationDidChangeNotification object:self userInfo:location];
12
        [[NSNotificationCenter defaultCenter] postNotification:notification1];
13
14
        // Update View

15
        [self updateView];
16
17
        // Request Location

18
        [self fetchWeatherData];
19
    }
20
}

Haben Sie sich jemals gefragt, warum ich in meinem Code so viele Hilfsmethoden verwende? Der Grund ist einfach. Durch das Umschließen von Funktionen in Hilfsmethoden ist es sehr einfach, Code an verschiedenen Stellen eines Projekts wiederzuverwenden. Der Hauptvorteil besteht jedoch darin, dass es beim Duplizieren von Code hilft. Das Duplizieren von Code sollten Sie immer so weit wie möglich vermeiden. Ein weiterer Vorteil der Verwendung von Hilfsmethoden besteht darin, dass Ihr Code dadurch besser lesbar wird. Durch das Erstellen von Methoden, die eine Funktion ausführen, und das Bereitstellen eines ausgewählten Methodennamens ist es einfacher, Ihren Code schnell zu lesen und zu verarbeiten.

Schritt 2: Senden der Anfrage

Es ist Zeit, die SVProgressHUD-Bibliothek zu verwenden. Ich mag diese Bibliothek wirklich, weil sie so einfach zu verwenden ist, ohne die Codebasis des Projekts zu überladen. Schauen Sie sich unten die Implementierung von fetchWeatherData an. Wir beginnen mit der Anzeige des Fortschritts-HUD und übergeben dann eine Struktur(CLLocationCoordinate2D) an die zuvor erstellte Convenience-Methode requestWeatherForCoordinate:completion:. Im Abschlussblock verbergen wir das Fortschritts-HUD und protokollieren die Antwort an der Konsole.

1
- (void)fetchWeatherData {
2
    // Show Progress HUD

3
    [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
4
5
    // Query Forecast API

6
    double lat = [[_location objectForKey:MTLocationKeyLatitude] doubleValue];
7
    double lng = [[_location objectForKey:MTLocationKeyLongitude] doubleValue];
8
    [[MTForecastClient sharedClient] requestWeatherForCoordinate:CLLocationCoordinate2DMake(lat, lng) completion:^(BOOL success, NSDictionary *response) {
9
        // Dismiss Progress HUD

10
        [SVProgressHUD dismiss];
11
12
        NSLog(@"Response > %@", response);
13
    }];
14
}

Importieren Sie vor dem Erstellen und Ausführen Ihrer Applikation die Header-Datei der MTForecastClient-Klasse in MTWeatherViewController.m.

1
#import "MTWeatherViewController.h"

2
3
#import "MTForecastClient.h"

4
5
@interface MTWeatherViewController () <CLLocationManagerDelegate> {
6
    BOOL _locationFound;
7
}
8
9
@property (strong, nonatomic) NSDictionary *location;
10
11
@property (strong, nonatomic) CLLocationManager *locationManager;
12
13
@end

Was passiert, wenn das Gerät nicht mit dem Internet verbunden ist? Haben Sie über dieses Szenario nachgedacht? In Bezug auf die Benutzererfahrung empfiehlt es sich, den Benutzer zu benachrichtigen, wenn die Anwendung keine Daten von der Vorhersage-API anfordern kann. Lassen Sie mich zeigen, wie das mit der AFNetworking-Bibliothek geht.


3. Erreichbarkeit

Es gibt eine Reihe von Bibliotheken, die diese Funktionalität bereitstellen, aber wir bleiben bei AFNetworking. Apple bietet auch Beispielcode an, der jedoch etwas veraltet ist und ARC nicht unterstützt.

AFNetworking hat Blöcke wirklich angenommen, was definitiv einer der Gründe ist, warum diese Bibliothek so populär geworden ist. Das Überwachen auf Änderungen der Erreichbarkeit ist so einfach wie das Übergeben eines Blocks an setReachabilityStatusChangeBlock:, eine weitere Methode der AFHTTPClient-Klasse. Der Block wird jedes Mal ausgeführt, wenn sich der Erreichbarkeitsstatus ändert, und akzeptiert ein einzelnes Argument vom Typ AFNetworkReachabilityStatus. Schauen Sie sich die aktualisierte initWithBaseURL: -Methode der MTForecastClient-Klasse an.

1
- (id)initWithBaseURL:(NSURL *)url {
2
    self = [super initWithBaseURL:url];
3
4
    if (self) {
5
        // Accept HTTP Header

6
        [self setDefaultHeader:@"Accept" value:@"application/json"];
7
8
        // Register HTTP Operation Class

9
        [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
10
11
        // Reachability

12
        __weak typeof(self)weakSelf = self;
13
        [self setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
14
            [[NSNotificationCenter defaultCenter] postNotificationName:MTRainReachabilityStatusDidChangeNotification object:weakSelf];
15
        }];
16
    }
17
18
    return self;
19
}

Um einen Aufbewahrungszyklus zu vermeiden, übergeben wir einen schwachen Verweis auf das Singleton-Objekt in dem Block, den wir an setReachabilityStatusChangeBlock: übergeben. Selbst wenn Sie ARC in Ihren Projekten verwenden, müssen Sie sich subtiler Speicherprobleme wie diesen bewusst sein. Der Name der Benachrichtigung, die wir veröffentlichen, ist eine weitere Zeichenfolgenkonstante, die in MTConstants.h/.m deklariert ist.

1
extern NSString * const MTRainReachabilityStatusDidChangeNotification;
1
NSString * const MTRainReachabilityStatusDidChangeNotification = @"com.mobileTuts.MTRainReachabilityStatusDidChangeNotification";

Der Grund für das Posten einer Benachrichtigung im Block zur Änderung des Erreichbarkeitsstatus besteht darin, dass andere Teile der Applikation leichter aktualisiert werden können, wenn sich die Erreichbarkeit des Geräts ändert. Um sicherzustellen, dass die MTWeatherViewController-Klasse über Änderungen der Erreichbarkeit benachrichtigt wird, werden Instanzen der Klasse als Beobachter für die vom Vorhersage-Client gesendeten Benachrichtigungen hinzugefügt (sehen Sie unten).

1
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
2
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
3
4
    if (self) {
5
        // Initialize Location Manager

6
        self.locationManager = [[CLLocationManager alloc] init];
7
8
        // Configure Location Manager

9
        [self.locationManager setDelegate:self];
10
        [self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
11
12
        // Add Observer

13
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
14
        [nc addObserver:self selector:@selector(reachabilityStatusDidChange:) name:MTRainReachabilityStatusDidChangeNotification object:nil];
15
    }
16
17
    return self;
18
}

Dies bedeutet auch, dass wir die Instanz als Beobachter in der dealloc-Methode entfernen müssen. Dies ist ein Detail, das oft übersehen wird.

1
- (void)dealloc {
2
    // Remove Observer

3
    [[NSNotificationCenter defaultCenter] removeObserver:self];
4
}

Die Implementierung von reachabilityStatusDidChange: ist im Moment ziemlich einfach. Wir werden die Implementierung aktualisieren, sobald wir die Benutzeroberfläche der Applikation erstellt haben.

1
- (void)reachabilityStatusDidChange:(NSNotification *)notification {
2
    MTForecastClient *forecastClient = [notification object];
3
    NSLog(@"Reachability Status > %i", forecastClient.networkReachabilityStatus);
4
}

4. Daten aktualisieren

Bevor wir diesen Beitrag abschließen, möchte ich zwei zusätzliche Funktionen hinzufügen:(1) Abrufen von Wetterdaten, wenn die Applikation aktiv wird, und (2) Hinzufügen der Möglichkeit, Wetterdaten manuell zu aktualisieren. Wir könnten einen Timer implementieren, der jede Stunde oder so neue Daten abruft, aber dies ist meiner Meinung nach für eine Wetteranwendung nicht erforderlich. Die meisten Benutzer starten die Applikation, sehen sich das Wetter an und stellen die Applikation in den Hintergrund. Es ist daher nur erforderlich, neue Daten abzurufen, wenn der Benutzer die Applikation startet. Dies bedeutet, dass wir in der MTWeatherViewController-Klasse auf UIApplicationDidBecomeActiveNotification-Benachrichtigungen warten müssen. Wie bei der Überwachung von Änderungen der Erreichbarkeit fügen wir Instanzen der Klasse als Beobachter von Benachrichtigungen vom Typ UIApplicationDidBecomeActiveNotification hinzu.

1
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
2
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
3
4
    if (self) {
5
        // Initialize Location Manager

6
        self.locationManager = [[CLLocationManager alloc] init];
7
8
        // Configure Location Manager

9
        [self.locationManager setDelegate:self];
10
        [self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
11
12
        // Add Observer

13
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
14
        [nc addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
15
        [nc addObserver:self selector:@selector(reachabilityStatusDidChange:) name:MTRainReachabilityStatusDidChangeNotification object:nil];
16
    }
17
18
    return self;
19
}

In applicationDidBecomeActive: überprüfen wir, ob diese location festgelegt ist (nicht nil), da dies nicht immer der Fall ist. Wenn der Standort gültig ist, rufen wir die Wetterdaten ab.

1
- (void)applicationDidBecomeActive:(NSNotification *)notification {
2
    if (self.location) {
3
        [self fetchWeatherData];
4
    }
5
}

Ich habe auch fetchWeatherData geändert, um die Vorhersage-API nur abzufragen, wenn das Gerät mit dem Web verbunden ist.

1
- (void)fetchWeatherData {
2
    if ([[MTForecastClient sharedClient] networkReachabilityStatus] == AFNetworkReachabilityStatusNotReachable) return;
3
4
    // Show Progress HUD

5
    [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
6
7
    // Query Forecast API

8
    double lat = [[_location objectForKey:MTLocationKeyLatitude] doubleValue];
9
    double lng = [[_location objectForKey:MTLocationKeyLongitude] doubleValue];
10
    [[MTForecastClient sharedClient] requestWeatherForCoordinate:CLLocationCoordinate2DMake(lat, lng) completion:^(BOOL success, NSDictionary *response) {
11
        // Dismiss Progress HUD

12
        [SVProgressHUD dismiss];
13
14
        // NSLog(@"Response > %@", response);

15
    }];
16
}

Fügen Sie dem Wetteransichts-Controller eine Schaltfläche hinzu, auf die der Benutzer tippen kann, um die Wetterdaten manuell zu aktualisieren. Erstellen Sie eine Steckdose in MTWeatherViewController.h und erstellen Sie eine refresh:-Aktion in MTWeatherViewController.m.

1
#import <UIKit/UIKit.h>

2
3
#import "MTLocationsViewController.h"

4
5
@interface MTWeatherViewController : UIViewController <MTLocationsViewControllerDelegate>
6
7
@property (weak, nonatomic) IBOutlet UILabel *labelLocation;
8
@property (weak, nonatomic) IBOutlet UIButton *buttonRefresh;
9
10
@end
1
- (IBAction)refresh:(id)sender {
2
    if (self.location) {
3
        [self fetchWeatherData];
4
    }
5
}

Öffnen Sie MTWeatherViewController.xib, fügen Sie der Ansicht des View Controllers eine Schaltfläche mit dem Titel Refresh hinzu und verbinden Sie den Ausgang und die Aktion mit der Schaltfläche (Abbildung 3). Der Grund für das Erstellen einer Steckdose für die Schaltfläche besteht darin, sie deaktivieren zu können, wenn keine Netzwerkverbindung verfügbar ist. Damit dies funktioniert, müssen wir die ReachabilityStatusDidChange: -Methode wie unten gezeigt aktualisieren.

Create a Weather App with Forecast – Forecast Integration - Adding a Refresh Button Create a Weather App with Forecast – Forecast Integration - Adding a Refresh Button Create a Weather App with Forecast – Forecast Integration - Adding a Refresh Button
Abbildung 3: Hinzufügen einer Aktualisierungsschaltfläche
1
- (void)reachabilityStatusDidChange:(NSNotification *)notification {
2
    MTForecastClient *forecastClient = [notification object];
3
    NSLog(@"Reachability Status > %i", forecastClient.networkReachabilityStatus);
4
5
    // Update Refresh Button

6
    self.buttonRefresh.enabled = (forecastClient.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable);
7
}

Es ist nicht erforderlich, die Aktualisierungsschaltfläche vorübergehend zu deaktivieren, wenn eine Anforderung in fetchWeatherData verarbeitet wird, da das Fortschritts-HUD der Ansicht des Ansichtscontrollers eine Ebene hinzufügt, die verhindert, dass der Benutzer mehrmals auf die Schaltfläche tippt. Erstellen Sie die Anwendung und führen Sie sie aus, um alles zu testen.


Bonus: Standorte entfernen

Ein Leser hat mich gefragt, wie Orte aus der Liste gelöscht werden sollen, damit ich sie der Vollständigkeit halber hier einbinde. Als erstes müssen wir der Tabellenansicht mitteilen, welche Zeilen durch Implementieren von tableView:canEditRowAtIndexPath: des UITableViewDataSource-Protokolls bearbeitet werden können. Diese Methode gibt YES zurück, wenn die Zeile bei indexPath bearbeitet werden kann, und NO, wenn dies nicht der Fall ist. Die Implementierung ist einfach, wie Sie unten sehen können. Jede Zeile kann bis auf die erste Zeile und den aktuell ausgewählten Speicherort bearbeitet werden.

1
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
2
    if (indexPath.row == 0) {
3
        return NO;
4
    }
5
6
    // Fetch Location

7
    NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)];
8
9
    return ![self isCurrentLocation:location];
10
}

Um zu überprüfen, ob location der aktuelle Standort ist, verwenden wir eine andere Hilfsmethode, isCurrentLocation:, bei der wir den aktuellen Standort abrufen und die Standortkoordinaten vergleichen. Es wäre besser (und einfacher) gewesen, wenn wir jedem in der Benutzerstandarddatenbank gespeicherten Speicherort eine eindeutige Kennung zugewiesen hätten. Dies würde nicht nur den Vergleich von Standorten erleichtern, sondern es würde uns auch ermöglichen, die eindeutige Kennung des aktuellen Standorts in der Standarddatenbank des Benutzers zu speichern und im Array von Standorten nachzuschlagen. Das Problem bei der aktuellen Implementierung besteht darin, dass Standorte mit genau denselben Koordinaten nicht voneinander unterschieden werden können.

1
- (BOOL)isCurrentLocation:(NSDictionary *)location {
2
    // Fetch Current Location

3
    NSDictionary *currentLocation = [[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocation];
4
5
    if ([location[MTLocationKeyLatitude] doubleValue] == [currentLocation[MTLocationKeyLatitude] doubleValue] &&
6
        [location[MTLocationKeyLongitude] doubleValue] == [currentLocation[MTLocationKeyLongitude] doubleValue]) {
7
        return YES;
8
    }
9
10
    return NO;
11
}

Wenn der Benutzer auf die Schaltfläche zum Löschen einer Tabellenansichtszeile tippt, wird der Datenquelle der Tabellenansicht eine tableView:commitEditingStyle: forRowAtIndexPath:-Nachricht gesendet. Bei dieser Methode müssen wir(1) die Datenquelle aktualisieren,(2) die Änderungen in der Standarddatenbank des Benutzers speichern und(3) die Tabellenansicht aktualisieren. Wenn editStyle gleich UITableViewCellEditingStyleDelete ist, entfernen wir den Speicherort aus dem location-Array und speichern das aktualisierte Array in der Standarddatenbank des Benutzers. Wir löschen die Zeile auch aus der Tabellenansicht, um die Änderung in der Datenquelle widerzuspiegeln.

1
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
2
    if (editingStyle == UITableViewCellEditingStyleDelete) {
3
        // Update Locations

4
        [self.locations removeObjectAtIndex:(indexPath.row - 1)];
5
6
        // Update User Defaults

7
        [[NSUserDefaults standardUserDefaults] setObject:self.locations forKey:MTRainUserDefaultsLocations];
8
9
        // Update Table View

10
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
11
    }
12
}

Um den Bearbeitungsstil der Tabellenansicht umzuschalten, müssen Sie der Benutzeroberfläche eine Bearbeitungsschaltfläche hinzufügen. Erstellen Sie in MTLocationsViewController.h eine Steckdose für die Schaltfläche und in MTLocationsViewController.m eine Aktion mit dem Namen editLocations. In editLocations: schalten wir den Bearbeitungsstil der Tabellenansicht um.

1
#import <UIKit/UIKit.h>

2
3
@protocol MTLocationsViewControllerDelegate;
4
5
@interface MTLocationsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
6
7
@property (weak, nonatomic) id<MTLocationsViewControllerDelegate> delegate;
8
9
@property (weak, nonatomic) IBOutlet UITableView *tableView;
10
@property (weak, nonatomic) IBOutlet UIBarButtonItem *editButton;
11
12
@end
13
14
@protocol MTLocationsViewControllerDelegate <NSObject>
15
- (void)controllerShouldAddCurrentLocation:(MTLocationsViewController *)controller;
16
- (void)controller:(MTLocationsViewController *)controller didSelectLocation:(NSDictionary *)location;
17
@end
1
- (IBAction)editLocations:(id)sender {
2
    [self.tableView setEditing:![self.tableView isEditing] animated:YES];
3
}

Öffnen Sie MTLocationsViewController.xib, fügen Sie eine Navigationsleiste zur Ansicht des View-Controllers hinzu und fügen Sie eine Bearbeitungsschaltfläche zur Navigationsleiste hinzu. Verbinden Sie die Schaltfläche "Bearbeiten" mit dem Ausgang und der Aktion, die wir soeben erstellt haben.

Create a Weather App with Forecast – Forecast Integration - Adding an Edit Button Create a Weather App with Forecast – Forecast Integration - Adding an Edit Button Create a Weather App with Forecast – Forecast Integration - Adding an Edit Button
Abbildung 4: Hinzufügen einer Bearbeitungsschaltfläche

Sie fragen sich vielleicht, warum wir eine Steckdose für die Schaltfläche "Bearbeiten" erstellt haben. Der Grund dafür ist, dass wir den Titel der Schaltfläche "Bearbeiten" von "Bearbeiten" in "Fertig" und umgekehrt ändern müssen, wenn sich der Bearbeitungsstil der Tabellenansicht ändert. Wenn der Benutzer den letzten Speicherort (mit Ausnahme des aktuellen Speicherorts) in der Tabellenansicht löscht, ist es außerdem hilfreich, den Bearbeitungsstil der Tabellenansicht automatisch umzuschalten. Diese Funktionen sind nicht schwer zu implementieren, weshalb ich sie Ihnen als Übung überlasse. Wenn Sie auf Probleme stoßen oder Fragen haben, können Sie in den Kommentaren unter diesem Artikel einen Kommentar hinterlassen.

Schlussfolgerung

Wir haben die Vorhersage-API erfolgreich in unsere Wetter-Applikation integriert. Im nächsten Tutorial werden wir uns auf die Benutzeroberfläche und das Design der Applikation konzentrieren.