Erstellen Sie eine Wetter-App mit Vorhersage - Benutzeroberfläche
German (Deutsch) translation by Valentina (you can also view the original English article)
Im letzten Artikel dieser Reihe werde ich Ihnen zeigen, wie Sie die Benutzeroberfläche unserer Wetteranwendung erstellen. Dank der Arbeit von Chris Carey, einem regelmäßigen Mitarbeiter von Vectortuts+, haben wir ein wunderschönes Design, mit dem wir arbeiten können!
Einführung
Für das Design unserer Wetteranwendung habe ich mit Chris Carey zusammengearbeitet, einem regelmäßigen Mitarbeiter von Vectortuts+. Chris hat ein wunderschönes Design entworfen, mit dem wir die Benutzeroberfläche der Anwendung erstellen können. Nachdem Chris mir das Design als Vektorillustration übergeben hatte, schnitt ich das Kunstwerk in Scheiben und bereitete es für die Verwendung in unserem Projekt vor. Sie finden das geschnittene Bildmaterial in den Quelldateien dieses Artikels.



Aufgrund der eingeschränkten Anpassbarkeit von UIKit müssen wir bei der Implementierung von Chris 'Design einige Kompromisse eingehen. Das Endergebnis wird jedoch sehr nach dem Design aussehen, das Chris für uns entworfen hat. Das Anpassen der UISwitch-Klasse von UIKit ist beispielsweise sehr begrenzt, und wir werden daher einen anderen Ansatz verwenden, um die Temperatureinstellung zu implementieren. Chris verwendete zwei benutzerdefinierte Schriftarten für sein Design, Maven Pro und Mission Gothic. Obwohl iOS benutzerdefinierte Schriftarten unterstützt, werden nur Schriftarten verwendet, die unter iOS verfügbar sind.
1. Benachrichtigungen
Schritt 1: Erstellen von String-Konstanten
Immer wenn der Wetteransichts-Controller Wetterdaten von der Vorhersage-API empfängt, müssen seine eigene Ansicht und die richtige Ansicht aktualisiert werden. Dies bedeutet, dass wir den Vorhersage-View Controller benachrichtigen und ihm die Wetterdaten senden müssen. Es gibt mehrere Möglichkeiten, den Controller der Vorhersageansicht über ein solches Ereignis zu informieren. Das Versenden einer Benachrichtigung mit NSNotificationCenter ist die einfachste Lösung und bietet uns die größte Flexibilität. Wir können dieselbe Lösung zum Aktualisieren der Benutzeroberfläche verwenden, nachdem der Benutzer die Temperatureinstellung umgeschaltet hat. Durch das Senden einer Benachrichtigung kann sich jedes an diesem Ereignis interessierte Objekt als Beobachter registrieren. Ich habe MTConstants.h/.m aktualisiert, um für jede Benachrichtigung eine Zeichenfolgenkonstante zu erstellen.
1 |
extern NSString * const MTRainWeatherDataDidChangeChangeNotification; |
2 |
extern NSString * const MTRainTemperatureUnitDidChangeNotification; |
1 |
NSString * const MTRainWeatherDataDidChangeChangeNotification = @"com.mobileTuts.MTRainWeatherDataDidChangeChangeNotification"; |
2 |
NSString * const MTRainTemperatureUnitDidChangeNotification = @"com.mobileTuts.MTRainTemperatureUnitDidChangeNotification"; |
Schritt 2: Wetter-View Controller
Nachdem wir eine Antwort von der Vorhersage-API erhalten haben response, müssen wir sie speichern, damit wir sie später zum Aktualisieren der Ansicht verwenden können. Erstellen Sie zwei private Eigenschaften in MTWeatherViewController.m: (1) response (vom Typ NSDictionary), die die Antwort der Vorhersage-API enthält, und (2) forecast (vom Typ NSArray), die eine Teilmenge der Antwort enthält, die Wetterdaten für die nächsten Stunden.
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 |
@property (strong, nonatomic) NSDictionary *response; |
11 |
@property (strong, nonatomic) NSArray *forecast; |
12 |
|
13 |
@property (strong, nonatomic) CLLocationManager *locationManager; |
14 |
|
15 |
@end
|
Um Benachrichtigungen zu erhalten, müssen wir initWithNibName:bundle: wie unten gezeigt aktualisieren (MTWeatherViewController.m). In weatherDataDidChangeChange: speichern wir die Antwort der Vorhersage-API in response und forecast und aktualisieren die Ansicht des View-Controllers. In temperaturUnitDidChange müssen wir nur die Ansicht aktualisieren, um die geänderte Einstellung widerzuspiegeln.
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 |
[nc addObserver:self selector:@selector(weatherDataDidChangeChange:) name:MTRainWeatherDataDidChangeChangeNotification object:nil]; |
17 |
[nc addObserver:self selector:@selector(temperatureUnitDidChange:) name:MTRainTemperatureUnitDidChangeNotification object:nil]; |
18 |
}
|
19 |
|
20 |
return self; |
21 |
}
|
1 |
- (void)weatherDataDidChangeChange:(NSNotification *)notification { |
2 |
// Update Response & Forecast
|
3 |
[self setResponse:[notification userInfo]]; |
4 |
[self setForecast:self.response[@"hourly"][@"data"]]; |
5 |
|
6 |
// Update View
|
7 |
[self updateView]; |
8 |
}
|
1 |
- (void)temperatureUnitDidChange:(NSNotification *)notification { |
2 |
// Update View
|
3 |
[self updateView]; |
4 |
}
|
Schritt 3: Vorhersage-View-Controller
Die Schritte sind in der MTForecastViewController-Klasse nahezu identisch. Wir aktualisieren initWithNibName:bundle: wie unten gezeigt, erstellen zwei Eigenschaften (response und forecast) und implementieren weatherDataDidChangeChange: und temperaturUnitDidChange:. Der Unterschied sind die in forecast gespeicherten Wetterdaten. Wir werden updateView etwas später in diesem Tutorial implementieren, aber es wird empfohlen, eine Stub-Implementierung zu erstellen, um alle Compiler-Warnungen zu entfernen.
1 |
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { |
2 |
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; |
3 |
|
4 |
if (self) { |
5 |
// Add Observer
|
6 |
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; |
7 |
[nc addObserver:self selector:@selector(weatherDataDidChangeChange:) name:MTRainWeatherDataDidChangeChangeNotification object:nil]; |
8 |
[nc addObserver:self selector:@selector(temperatureUnitDidChange:) name:MTRainTemperatureUnitDidChangeNotification object:nil]; |
9 |
}
|
10 |
|
11 |
return self; |
12 |
}
|
1 |
#import "MTForecastViewController.h"
|
2 |
|
3 |
@interface MTForecastViewController () |
4 |
|
5 |
@property (strong, nonatomic) NSDictionary *response; |
6 |
@property (strong, nonatomic) NSArray *forecast; |
7 |
|
8 |
@end
|
1 |
- (void)weatherDataDidChangeChange:(NSNotification *)notification { |
2 |
// Update Response & Forecast
|
3 |
[self setResponse:[notification userInfo]]; |
4 |
[self setForecast:self.response[@"daily"][@"data"]]; |
5 |
|
6 |
// Update View
|
7 |
[self updateView]; |
8 |
}
|
1 |
- (void)temperatureUnitDidChange:(NSNotification *)notification { |
2 |
// Update View
|
3 |
[self updateView]; |
4 |
}
|
1 |
- (void)updateView { |
2 |
|
3 |
}
|
2.Benutzeroberfläche Center-Ansicht
Schritt 1: Ausgänge und Aktionen
Obwohl die mittlere Ansicht viele Informationen enthält, ist die Implementierung nicht so schwierig. Beginnen wir mit der Erstellung einer Reihe von Verkaufsstellen und einer neuen Aktion. Aktualisieren Sie MTWeatherViewController.h wie unten gezeigt. Überdenken Sie Chris Design, um den Ort und den Zweck jedes Benutzeroberflächenelements besser zu verstehen. Der Unterschied zu Chris Design besteht darin, dass wir das Kalendersymbol oben rechts durch die Schaltfläche "Aktualisieren" ersetzen, die wir im vorherigen Tutorial erstellt haben. Die Wetterdaten für die nächsten Stunden werden in einer Sammlungsansicht dargestellt. Dies bedeutet, dass die MTWeatherViewController-Klasse den Protokollen UICollectionViewDataSource, UICollectionViewDelegate und UICollectionViewDelegateFlowLayout entsprechen muss.
1 |
#import <UIKit/UIKit.h>
|
2 |
|
3 |
#import "MTLocationsViewController.h"
|
4 |
|
5 |
@interface MTWeatherViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, MTLocationsViewControllerDelegate> |
6 |
|
7 |
@property (weak, nonatomic) IBOutlet UIButton *buttonLocation; |
8 |
@property (weak, nonatomic) IBOutlet UIButton *buttonRefresh; |
9 |
|
10 |
@property (weak, nonatomic) IBOutlet UILabel *labelDate; |
11 |
@property (weak, nonatomic) IBOutlet UILabel *labelTemp; |
12 |
@property (weak, nonatomic) IBOutlet UILabel *labelTime; |
13 |
@property (weak, nonatomic) IBOutlet UILabel *labelWind; |
14 |
@property (weak, nonatomic) IBOutlet UILabel *labelRain; |
15 |
@property (weak, nonatomic) IBOutlet UILabel *labelLocation; |
16 |
|
17 |
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView; |
18 |
|
19 |
@end
|
Wir müssen auch eine Aktion für die Standortschaltfläche oben links hinzufügen. Öffnen Sie MTWeatherViewController.m und fügen Sie eine neue Aktion mit dem Namen openLeftView: hinzu. In openLeftView: weisen wir den View Deck Controller des View Controllers an, die linke Ansicht umzuschalten. Denken Sie daran, dass Sie auch von links nach rechts wischen können, um die linke Ansicht zu öffnen.
1 |
- (IBAction)openLeftView:(id)sender { |
2 |
[self.viewDeckController toggleLeftViewAnimated:YES]; |
3 |
}
|
Schritt 2: Erstellen der Benutzeroberfläche
Öffnen Sie MTWeatherViewController.xib und erstellen Sie die Benutzeroberfläche wie in Abbildung 2 dargestellt. Beim Erstellen der Benutzeroberfläche ist es wichtig zu überprüfen, ob die Benutzeroberfläche sowohl auf dem 3,5-Zoll-Bildschirm als auch auf dem 4-Zoll-Bildschirm des iPhone 5 korrekt angezeigt wird. Sie können dies testen, indem Sie die Ansicht des View Controllers auswählen und das Attribut Größe im Attributinspektor ändern. Um das gewünschte Ergebnis zu erzielen, müssen Sie die Autolayout-Einschränkungen der Benutzeroberflächenelemente anpassen. Ziel ist es, dass die Wetterdaten oben in der Ansicht bleiben, während die Sammlungsansicht unten geklebt wird. Die Symbole neben den Beschriftungen für Zeit, Wind und Regen sind Instanzen von UIImageView.



Konfigurieren Sie die Beschriftungen und Schaltflächen wie in Abbildung 2 dargestellt. Dazu gehört das ordnungsgemäße Ausrichten des Beschriftungstextes, das Festlegen der Typen beider Schaltflächen auf Benutzerdefiniert, das Festlegen des Dateibesitzers als dataSource und delegate der Sammlungsansicht sowie das Festlegen der Bildlaufrichtung der Sammlungsansicht Flusslayout nach horizontal. Ich bin ein Fan von Gill Sans, daher habe ich diese Schriftart für dieses Projekt ausgewählt. Verbinden Sie die zuvor erstellten Steckdosen und Aktionen, bevor Sie zur Implementierungsdatei des Wetter-View Controllers zurückkehren. Zusätzlich zu den Beschriftungen und Schaltflächen habe ich der Ansicht des View-Controllers eine Bildansicht hinzugefügt, um das Hintergrundbild anzuzeigen.
Wie bereits in der Einleitung erwähnt, finden Sie die Grafik der Anwendung in den Quelldateien dieses Tutorials. Erstellen Sie in Ihrem Xcode-Projekt einen Ordner mit dem Namen Artwork und ziehen Sie das Artwork in diesen Ordner.
Schritt 3: Auffüllen der Benutzeroberfläche
Derzeit protokollieren wir die Antwort der Vorhersage-API in der Xcode-Konsole. Um die Wetterdaten verwenden zu können, müssen wir die fetchWeatherData-Methode wie unten gezeigt aktualisieren. Im Abschlussblock von requestWeatherForCoordinate:completion: verbergen wir das Fortschritts-HUD und senden eine Benachrichtigung an den Hauptthread. Wir verwenden die Funktion dispatch_async und übergeben die Warteschlange des Hauptthreads als erstes Argument. Das userInfo-Wörterbuch der Benachrichtigung ist die Antwort auf die Anforderung.
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 |
if (response && [response isKindOfClass:[NSDictionary class]]) { |
15 |
dispatch_async(dispatch_get_main_queue(), ^{ |
16 |
// Post Notification on Main Thread
|
17 |
NSNotification *notification = [NSNotification notificationWithName:MTRainWeatherDataDidChangeChangeNotification object:nil userInfo:response]; |
18 |
[[NSNotificationCenter defaultCenter] postNotification:notification]; |
19 |
});
|
20 |
}
|
21 |
}];
|
22 |
}
|
Die Wetter- und Vorhersage-View-Controller sind beide Beobachter von MTRainWeatherDataDidChangeChangeNotification-Benachrichtigungen. Der Weather View Controller ruft weatherDataDidChangeChange: auf, was wiederum updateView aufruft. In updateView rufen wir updateCurrentWeather auf und aktualisieren die Sammlungsansicht, indem wir eine Nachricht von reloadData senden.
1 |
- (void)updateView { |
2 |
// Update Location Label
|
3 |
[self.labelLocation setText:[self.location objectForKey:MTLocationKeyCity]]; |
4 |
|
5 |
// Update Current Weather
|
6 |
[self updateCurrentWeather]; |
7 |
|
8 |
// Reload Collection View
|
9 |
[self.collectionView reloadData]; |
10 |
}
|
Bevor wir updateCurrentWeather implementieren, möchte ich einen kleinen Umweg machen. Wir werden Temperaturwerte an verschiedenen Stellen der Anwendung anzeigen. Da wir sowohl Fahrenheit als auch Celsius unterstützen, kann dies umständlich werden. Es ist daher nützlich, eine Klasse zu erstellen, die diese Logik zentralisiert, damit wir unsere Codebasis nicht mit if-Anweisungen und Temperaturkonvertierungen überladen müssen.
Schritt 4: Erstellen der Einstellungsklasse
Bevor wir die Klasse erstellen, die Temperaturkonvertierungen verarbeitet, müssen wir in der Lage sein, die aktuelle Temperatureinstellung in der Standarddatenbank des Benutzers zu speichern. Besuchen Sie MTConstants.h/.m erneut und deklarieren Sie eine Zeichenfolgenkonstante mit dem Namen MTRainUserDefaultsTemperatureUnit.
1 |
extern NSString * const MTRainUserDefaultsTemperatureUnit; |
1 |
NSString * const MTRainUserDefaultsTemperatureUnit = @"temperatureUnit"; |
Um das Arbeiten mit Einstellungen zu vereinfachen, erstelle ich häufig eine Kategorie in NSUserDefaults, mit der ich schnell und elegant auf die Einstellungen der Anwendung zugreifen kann. Lassen Sie mich Ihr zeigen, was ich meine. Erstellen Sie eine neue Objective-C-Kategorie (Abbildung 3), benennen Sie die Kategorie Helfer und machen Sie sie zu einer Kategorie in NSUserDefaults (Abbildung 4). In NSUserDefaults + Helpers.h deklarieren wir drei Klassenmethoden, wie unten gezeigt.






1 |
#import <Foundation/Foundation.h>
|
2 |
|
3 |
@interface NSUserDefaults (Helpers) |
4 |
|
5 |
#pragma mark -
|
6 |
#pragma mark Temperature
|
7 |
+ (BOOL)isDefaultCelcius; |
8 |
+ (void)setDefaultToCelcius; |
9 |
+ (void)setDefaultToFahrenheit; |
10 |
|
11 |
@end
|
Obwohl diese Methoden nicht magisch sind, sind sie sehr nützlich. Die erste Methode, isDefaultCelcius, gibt an, ob die Temperatureinheit auf Celsius eingestellt ist oder nicht. Die beiden anderen Methoden machen es sehr einfach, zwischen Fahrenheit und Celsius zu wechseln. Wir aktualisieren nicht nur die Benutzerstandarddatenbank, sondern veröffentlichen auch eine Benachrichtigung, die die Beobachter über die Änderung informiert.
1 |
+ (BOOL)isDefaultCelcius { |
2 |
return [[NSUserDefaults standardUserDefaults] integerForKey:MTRainUserDefaultsTemperatureUnit] == 1; |
3 |
}
|
4 |
|
5 |
+ (void)setDefaultToCelcius { |
6 |
// Update User Defaults
|
7 |
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; |
8 |
[ud setInteger:1 forKey:MTRainUserDefaultsTemperatureUnit]; |
9 |
[ud synchronize]; |
10 |
|
11 |
// Post Notification
|
12 |
[[NSNotificationCenter defaultCenter] postNotificationName:MTRainTemperatureUnitDidChangeNotification object:nil]; |
13 |
}
|
14 |
|
15 |
+ (void)setDefaultToFahrenheit { |
16 |
// Update User Defaults
|
17 |
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; |
18 |
[ud setInteger:0 forKey:MTRainUserDefaultsTemperatureUnit]; |
19 |
[ud synchronize]; |
20 |
|
21 |
// Post Notification
|
22 |
[[NSNotificationCenter defaultCenter] postNotificationName:MTRainTemperatureUnitDidChangeNotification object:nil]; |
23 |
}
|
Es ist Zeit, die Einstellungsklasse zu erstellen, über die ich zuvor geschrieben habe. Die Lösung ist überraschend einfach. Erstellen Sie eine neue Objective-C-Klasse, nennen Sie sie MTSettings und machen Sie sie zu einer Unterklasse von NSObject (Abbildung 5). In MTSettings.h deklarieren wir eine Klassenmethode, formatTemperature:. In MTSettings.m importieren wir den Header der Kategorie, die wir vor kurzem erstellt haben, und implementieren formatTemperature: wie unten gezeigt. Die Methode akzeptiert eine Instanz von NSNumber, konvertiert sie in einen Float und gibt eine formatierte Zeichenfolge zurück, die auf der Temperatureinstellung basiert.



1 |
#import <Foundation/Foundation.h>
|
2 |
|
3 |
@interface MTSettings : NSObject |
4 |
|
5 |
#pragma mark -
|
6 |
#pragma mark Convenience Methods
|
7 |
+ (NSString *)formatTemperature:(NSNumber *)temperature; |
8 |
|
9 |
@end
|
1 |
#import "NSUserDefaults+Helpers.h"
|
1 |
+ (NSString *)formatTemperature:(NSNumber *)temperature { |
2 |
float value = [temperature floatValue]; |
3 |
|
4 |
if ([NSUserDefaults isDefaultCelcius]) { |
5 |
value = (value - 32.0) * (5.0 / 9.0); |
6 |
}
|
7 |
|
8 |
return [NSString stringWithFormat:@"%.0f°", value]; |
9 |
}
|
Bevor wir fortfahren, fügen Sie der vorkompilierten Header-Datei des Projekts eine Importanweisung für die MTSettings-Klasse hinzu, damit wir sie im gesamten Projekt verwenden können.
1 |
#import <Availability.h>
|
2 |
|
3 |
#ifndef __IPHONE_3_0
|
4 |
#warning "This project uses features only available in iOS SDK 3.0 and later."
|
5 |
#endif
|
6 |
|
7 |
#ifdef __OBJC__
|
8 |
#import <UIKit/UIKit.h>
|
9 |
#import <Foundation/Foundation.h>
|
10 |
#import <QuartzCore/QuartzCore.h>
|
11 |
#import <CoreLocation/CoreLocation.h>
|
12 |
#import <MobileCoreServices/MobileCoreServices.h>
|
13 |
#import <SystemConfiguration/SystemConfiguration.h>
|
14 |
|
15 |
#import "AFNetworking.h"
|
16 |
#import "SVProgressHUD.h"
|
17 |
#import "IIViewDeckController.h"
|
18 |
|
19 |
#import "MTSettings.h"
|
20 |
#import "MTConstants.h"
|
21 |
#endif
|
Es ist jetzt an der Zeit, updateCurrentWeather in der MTWeatherViewController-Klasse zu implementieren. Die Daten für das aktuelle Wetter sind eine Teilmenge der Antwort, die wir von der Vorhersage-API erhalten haben. Die Implementierung von updateCurrentWeather ist ziemlich einfach. Die einzige Einschränkung, auf die Sie achten müssen, ist die Niederschlagswahrscheinlichkeit. Wenn dieser Wert gleich 0 ist, ist die precipProbability-Schlüssel nicht in der Antwort enthalten. Aus diesem Grund prüfen wir zunächst, ob im Antwortwörterbuch die precipProbability-Schlüssel vorhanden ist.
1 |
- (void)updateCurrentWeather { |
2 |
// Weather Data
|
3 |
NSDictionary *data = [self.response objectForKey:@"currently"]; |
4 |
|
5 |
// Update Date Label
|
6 |
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; |
7 |
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; |
8 |
[dateFormatter setDateFormat:@"EEEE, MMM d"]; |
9 |
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@"time"] doubleValue]]; |
10 |
[self.labelDate setText:[dateFormatter stringFromDate:date]]; |
11 |
|
12 |
// Update Temperature Label
|
13 |
[self.labelTemp setText:[MTSettings formatTemperature:data[@"temperature"]]]; |
14 |
|
15 |
// Update Time Label
|
16 |
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init]; |
17 |
[timeFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; |
18 |
[timeFormatter setDateFormat:@"ha"]; |
19 |
[self.labelTime setText:[timeFormatter stringFromDate:[NSDate date]]]; |
20 |
|
21 |
// Update Wind Label
|
22 |
[self.labelWind setText:[NSString stringWithFormat:@"%.0fMP", [data[@"windSpeed"] floatValue]]]; |
23 |
|
24 |
// Update Rain Label
|
25 |
float rainProbability = 0.0; |
26 |
if (data[@"precipProbability"]) { |
27 |
rainProbability = [data[@"precipProbability"] floatValue] * 100.0; |
28 |
}
|
29 |
|
30 |
[self.labelRain setText:[NSString stringWithFormat:@"%.0f%%", rainProbability]]; |
31 |
}
|
Schritt 5: Auffüllen der Sammlungsansicht
Um die Sammlungsansicht zu füllen, müssen wir zuerst eine UICollectionViewCell-Unterklasse erstellen. Erstellen Sie eine neue Objective-C-Klasse, nennen Sie sie MTHourCell und machen Sie sie zu einer Unterklasse von UICollectionViewCell (Abbildung 6). Das Erstellen von benutzerdefinierten Tabellen- oder Sammlungsansichtszellen kann mühsam und mühsam sein. Wenn Sie mehr über das Erstellen von benutzerdefinierten Tabellen- und Sammlungsansichtszellen erfahren möchten, empfehlen wir Ihnen, sich ein Tutorial anzusehen, das ich vor einigen Wochen geschrieben habe.



In der Schnittstelle von MTHourCell deklarieren wir vier Eigenschaften vom Typ UILabel. Wir machen nicht viel Magie in MTHourcell.m, wie Sie unten sehen können. Um die Methode initWithFrame: besser zu verstehen, besuchen Sie das Design, das ich Ihnen am Anfang dieses Artikels gezeigt habe. Ich werde die Implementierung von initWithFrame: im Detail nicht diskutieren, aber ich möchte darauf hinweisen, dass ich eine Präprozessordefinition für die Textfarbe der Beschriftungen verwende. Ich habe die Vorprozessdefinition zu MTConstants.h hinzugefügt, um sie für das gesamte Projekt verfügbar zu machen (sehen Sie unten).
1 |
#import <UIKit/UIKit.h>
|
2 |
|
3 |
@interface MTHourCell : UICollectionViewCell |
4 |
|
5 |
@property (strong, nonatomic) UILabel *labelTime; |
6 |
@property (strong, nonatomic) UILabel *labelTemp; |
7 |
@property (strong, nonatomic) UILabel *labelWind; |
8 |
@property (strong, nonatomic) UILabel *labelRain; |
9 |
|
10 |
@end
|
1 |
#import "MTHourCell.h"
|
2 |
|
3 |
#define kMTLabelBottomWidth 40.0
|
4 |
#define kMTLabelBottomHeight 40.0
|
5 |
|
6 |
@interface MTHourCell () |
7 |
|
8 |
@end
|
9 |
|
10 |
@implementation MTHourCell |
11 |
|
12 |
- (id)initWithFrame:(CGRect)frame { |
13 |
self = [super initWithFrame:frame]; |
14 |
|
15 |
if (self) { |
16 |
// Helpers
|
17 |
CGSize size = self.contentView.frame.size; |
18 |
|
19 |
// Initialize Label Time
|
20 |
self.labelTime = [[UILabel alloc] initWithFrame:CGRectMake(30.0, 0.0, 50.0, 40.0)]; |
21 |
|
22 |
// Configure Label Time
|
23 |
[self.labelTime setBackgroundColor:[UIColor clearColor]]; |
24 |
[self.labelTime setTextColor:[UIColor whiteColor]]; |
25 |
[self.labelTime setFont:[UIFont fontWithName:@"GillSans-Light" size:18.0]]; |
26 |
[self.labelTime setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin)]; |
27 |
[self.contentView addSubview:self.labelTime]; |
28 |
|
29 |
// Initialize Label Temp
|
30 |
self.labelTemp = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 46.0, 80.0, 44.0)]; |
31 |
|
32 |
// Configure Label Temp
|
33 |
[self.labelTemp setBackgroundColor:[UIColor clearColor]]; |
34 |
[self.labelTemp setTextAlignment:NSTextAlignmentCenter]; |
35 |
[self.labelTemp setTextColor:kMTColorGray]; |
36 |
[self.labelTemp setFont:[UIFont fontWithName:@"GillSans-Bold" size:40.0]]; |
37 |
[self.labelTemp setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin)]; |
38 |
[self.contentView addSubview:self.labelTemp]; |
39 |
|
40 |
// Initialize Label Wind
|
41 |
self.labelWind = [[UILabel alloc] initWithFrame:CGRectMake(0.0, size.height - kMTLabelBottomHeight, kMTLabelBottomWidth, kMTLabelBottomHeight)]; |
42 |
|
43 |
// Configure Label Wind
|
44 |
[self.labelWind setBackgroundColor:[UIColor clearColor]]; |
45 |
[self.labelWind setTextAlignment:NSTextAlignmentCenter]; |
46 |
[self.labelWind setTextColor:kMTColorGray]; |
47 |
[self.labelWind setFont:[UIFont fontWithName:@"GillSans-Light" size:16.0]]; |
48 |
[self.labelWind setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)]; |
49 |
[self.contentView addSubview:self.labelWind]; |
50 |
|
51 |
// Initialize Label Rain
|
52 |
self.labelRain = [[UILabel alloc] initWithFrame:CGRectMake(size.width - kMTLabelBottomWidth, size.height - kMTLabelBottomHeight, kMTLabelBottomWidth, kMTLabelBottomHeight)]; |
53 |
|
54 |
// Configure Label Rain
|
55 |
[self.labelRain setBackgroundColor:[UIColor clearColor]]; |
56 |
[self.labelRain setTextAlignment:NSTextAlignmentCenter]; |
57 |
[self.labelRain setTextColor:kMTColorGray]; |
58 |
[self.labelRain setFont:[UIFont fontWithName:@"GillSans-Light" size:16.0]]; |
59 |
[self.labelRain setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)]; |
60 |
[self.contentView addSubview:self.labelRain]; |
61 |
|
62 |
// Background View
|
63 |
UIImage *backgroundImage = [[UIImage imageNamed:@"background-hour-cell"] resizableImageWithCapInsets:UIEdgeInsetsMake(40.0, 10.0, 10.0, 10.0)]; |
64 |
UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, size.width, size.height)]; |
65 |
[backgroundView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; |
66 |
[backgroundView setImage:backgroundImage]; |
67 |
[self setBackgroundView:backgroundView]; |
68 |
}
|
69 |
|
70 |
return self; |
71 |
}
|
72 |
|
73 |
@end
|
1 |
#define kMTColorGray [UIColor colorWithRed:0.737 green:0.737 blue:0.737 alpha:1.0]
|
2 |
#define kMTColorGreen [UIColor colorWithRed:0.325 green:0.573 blue:0.388 alpha:1.0]
|
3 |
#define kMTColorOrange [UIColor colorWithRed:1.000 green:0.306 blue:0.373 alpha:1.0]
|
Wie Sie unten sehen können, ist die Implementierung der Protokolle UICollectionViewDataSource, UICollectionViewDelegate und UICollectionViewDelegateFlowLayout der Implementierung der Protokolle UITableViewDataSource und UITableViewDelegate sehr ähnlich.
1 |
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { |
2 |
return self.forecast ? 1 : 0; |
3 |
}
|
4 |
|
5 |
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { |
6 |
return [self.forecast count]; |
7 |
}
|
8 |
|
9 |
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { |
10 |
MTHourCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:HourCell forIndexPath:indexPath]; |
11 |
|
12 |
// Fetch Data
|
13 |
NSDictionary *data = [self.forecast objectAtIndex:indexPath.row]; |
14 |
|
15 |
// Initialize Date Formatter
|
16 |
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init]; |
17 |
[timeFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; |
18 |
[timeFormatter setDateFormat:@"ha"]; |
19 |
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@"time"] doubleValue]]; |
20 |
|
21 |
// Configure Cell
|
22 |
[cell.labelTime setText:[timeFormatter stringFromDate:date]]; |
23 |
[cell.labelTemp setText:[MTSettings formatTemperature:data[@"temperature"]]]; |
24 |
[cell.labelWind setText:[NSString stringWithFormat:@"%.0fMP", [data[@"windSpeed"] floatValue]]]; |
25 |
|
26 |
float rainProbability = 0.0; |
27 |
if (data[@"precipProbability"]) { |
28 |
rainProbability = [data[@"precipProbability"] floatValue] * 100.0; |
29 |
}
|
30 |
|
31 |
[cell.labelRain setText:[NSString stringWithFormat:@"%.0f%%", rainProbability]]; |
32 |
|
33 |
return cell; |
34 |
}
|
1 |
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { |
2 |
return CGSizeMake(80.0, 120.0); |
3 |
}
|
4 |
|
5 |
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { |
6 |
return UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0); |
7 |
}
|
Damit dies alles funktioniert, müssen wir (1) die Header-Datei von MTHourCell importieren, (2) eine statische Zeichenfolgenkonstante deklarieren, die als Kennung für die Wiederverwendung von Zellen dient, und(3) die Auflistungsansicht anweisen, die MTHourCell-Klasse zum Instanziieren zu verwenden neue Zellen. In viewDidLoad setzen wir auch die Hintergrundfarbe der Sammlungsansicht auf transparent.
1 |
#import "MTHourCell.h"
|
1 |
static NSString *HourCell = @"HourCell"; |
1 |
- (void)viewDidLoad { |
2 |
[super viewDidLoad]; |
3 |
|
4 |
// Load Location
|
5 |
self.location = [[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocation]; |
6 |
|
7 |
if (!self.location) { |
8 |
[self.locationManager startUpdatingLocation]; |
9 |
}
|
10 |
|
11 |
// Configure Collection View
|
12 |
[self.collectionView setBackgroundColor:[UIColor clearColor]]; |
13 |
[self.collectionView registerClass:[MTHourCell class] forCellWithReuseIdentifier:HourCell]; |
14 |
}
|
3. Benutzeroberfläche Rechte Ansicht
Schritt 1: Ausgänge
Obwohl in der rechten Ansicht viele Daten angezeigt werden, ist die Implementierung der MTForecastViewController-Klasse nicht so komplex. Wir beginnen mit der Erstellung eines Ausgangs für die Tabellenansicht in MTForecastViewController.h und passen die Klasse an die Protokolle UITableViewDataSource und UITableViewDelegate an.
1 |
#import <UIKit/UIKit.h>
|
2 |
|
3 |
@interface MTForecastViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> |
4 |
|
5 |
@property (weak, nonatomic) IBOutlet UITableView *tableView; |
6 |
|
7 |
@end
|
Schritt 2: Erstellen der Benutzeroberfläche
Das Erstellen der Benutzeroberfläche ist so einfach wie das Hinzufügen einer Tabellenansicht zur Ansicht des View-Controllers, das Verbinden der vor kurzem erstellten Steckdose und das Festlegen des Dateibesitzers als dataSource und delegate der Tabellenansicht (Abbildung 7).



Schritt 3: Auffüllen der Tabellenansicht
Bevor wir die Tabellenansicht mit Daten füllen, müssen wir eine UITableViewCell-Unterklasse erstellen. Erstellen Sie eine neue Objective-C-Klasse, nennen Sie sie MTDayCell und machen Sie sie zu einer Unterklasse von UITableViewCell (Abbildung 8). Öffnen Sie MTDayCell.h und deklarieren Sie fünf Steckdosen vom Typ UILabel. Wie bei der MTHourCell-Klasse ist die Implementierung von MTDayCell nicht allzu schwierig, wie Sie unten sehen können.



1 |
#import <UIKit/UIKit.h>
|
2 |
|
3 |
@interface MTDayCell : UITableViewCell |
4 |
|
5 |
@property (strong, nonatomic) UILabel *labelDay; |
6 |
@property (strong, nonatomic) UILabel *labelDate; |
7 |
@property (strong, nonatomic) UILabel *labelTemp; |
8 |
@property (strong, nonatomic) UILabel *labelWind; |
9 |
@property (strong, nonatomic) UILabel *labelRain; |
10 |
|
11 |
@end
|
1 |
#import "MTDayCell.h"
|
2 |
|
3 |
#define kMTCalendarWidth 44.0
|
4 |
#define kMTCalendarHeight 80.0
|
5 |
#define kMTCalendarMarginLeft 60.0
|
6 |
#define kMTLabelRightWidth 30.0
|
7 |
#define kMTLabelRightHeight 14.0
|
8 |
|
9 |
@interface MTDayCell () |
10 |
|
11 |
@property (strong, nonatomic) UIImageView *imageViewCalendar; |
12 |
|
13 |
@end
|
14 |
|
15 |
@implementation MTDayCell |
16 |
|
17 |
#pragma mark -
|
18 |
#pragma mark Initialization
|
19 |
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { |
20 |
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; |
21 |
|
22 |
if (self) { |
23 |
// Helpers
|
24 |
CGSize size = self.contentView.frame.size; |
25 |
|
26 |
// Configure Table View Cell
|
27 |
[self setSelectionStyle:UITableViewCellSelectionStyleNone]; |
28 |
|
29 |
// Initialize Image View Clock
|
30 |
self.imageViewCalendar = [[UIImageView alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 0.0, kMTCalendarWidth, kMTCalendarHeight)]; |
31 |
|
32 |
// Configure Image View Clock
|
33 |
[self.imageViewCalendar setContentMode:UIViewContentModeCenter]; |
34 |
[self.imageViewCalendar setImage:[UIImage imageNamed:@"background-calendar-day-cell"]]; |
35 |
[self.imageViewCalendar setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)]; |
36 |
[self.contentView addSubview:self.imageViewCalendar]; |
37 |
|
38 |
// Initialize Label Day
|
39 |
self.labelDay = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 10.0, kMTCalendarWidth, 20.0)]; |
40 |
|
41 |
// Configure Label Day
|
42 |
[self.labelDay setTextColor:[UIColor whiteColor]]; |
43 |
[self.labelDay setTextAlignment:NSTextAlignmentCenter]; |
44 |
[self.labelDay setBackgroundColor:[UIColor clearColor]]; |
45 |
[self.labelDay setFont:[UIFont fontWithName:@"GillSans" size:14.0]]; |
46 |
[self.labelDay setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)]; |
47 |
[self.contentView addSubview:self.labelDay]; |
48 |
|
49 |
// Initialize Label Date
|
50 |
self.labelDate = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 20.0, kMTCalendarWidth, 60.0)]; |
51 |
|
52 |
// Configure Label Date
|
53 |
[self.labelDate setTextColor:kMTColorGray]; |
54 |
[self.labelDate setTextAlignment:NSTextAlignmentCenter]; |
55 |
[self.labelDate setBackgroundColor:[UIColor clearColor]]; |
56 |
[self.labelDate setFont:[UIFont fontWithName:@"GillSans" size:24.0]]; |
57 |
[self.labelDate setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)]; |
58 |
[self.contentView addSubview:self.labelDate]; |
59 |
|
60 |
// Initialize Label Wind
|
61 |
self.labelWind = [[UILabel alloc] initWithFrame:CGRectMake(size.width - kMTLabelRightWidth, (size.height / 2.0) - kMTLabelRightHeight, kMTLabelRightWidth, kMTLabelRightHeight)]; |
62 |
|
63 |
// Configure Label Wind
|
64 |
[self.labelWind setTextColor:kMTColorGray]; |
65 |
[self.labelWind setTextAlignment:NSTextAlignmentCenter]; |
66 |
[self.labelWind setBackgroundColor:[UIColor clearColor]]; |
67 |
[self.labelWind setFont:[UIFont fontWithName:@"GillSans" size:12.0]]; |
68 |
[self.labelWind setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)]; |
69 |
[self.contentView addSubview:self.labelWind]; |
70 |
|
71 |
// Initialize Label Rain
|
72 |
self.labelRain = [[UILabel alloc] initWithFrame:CGRectMake(size.width - kMTLabelRightWidth, (size.height / 2.0), kMTLabelRightWidth, kMTLabelRightHeight)]; |
73 |
|
74 |
// Configure Label Rain
|
75 |
[self.labelRain setTextColor:kMTColorGray]; |
76 |
[self.labelRain setTextAlignment:NSTextAlignmentCenter]; |
77 |
[self.labelRain setBackgroundColor:[UIColor clearColor]]; |
78 |
[self.labelRain setFont:[UIFont fontWithName:@"GillSans" size:12.0]]; |
79 |
[self.labelRain setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)]; |
80 |
[self.contentView addSubview:self.labelRain]; |
81 |
|
82 |
// Initialize Label Temp
|
83 |
self.labelTemp = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarWidth + kMTCalendarMarginLeft + 12.0, 0.0, size.width - kMTCalendarWidth - kMTCalendarMarginLeft - kMTLabelRightWidth - 12.0, size.height)]; |
84 |
|
85 |
// Configure Label Temp
|
86 |
[self.labelTemp setTextColor:kMTColorGray]; |
87 |
[self.labelTemp setTextAlignment:NSTextAlignmentCenter]; |
88 |
[self.labelTemp setBackgroundColor:[UIColor clearColor]]; |
89 |
[self.labelTemp setFont:[UIFont fontWithName:@"GillSans-Bold" size:40.0]]; |
90 |
[self.labelTemp setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; |
91 |
[self.contentView addSubview:self.labelTemp]; |
92 |
}
|
93 |
|
94 |
return self; |
95 |
}
|
96 |
|
97 |
@end
|
Die Implementierung des Datenquellenprotokolls für die Tabellenansicht ist der des Datenquellenprotokolls für die Sammlungsansicht, das wir zuvor gesehen haben, sehr ähnlich. Wir implementieren auch eine Methode des Tabellenansicht-Delegatenprotokolls, tableView:heightForRowAtIndexPath:, um die Zeilenhöhe auf 80.0 festzulegen.
1 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { |
2 |
return self.forecast ? 1 : 0; |
3 |
}
|
4 |
|
5 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { |
6 |
return [self.forecast count]; |
7 |
}
|
8 |
|
9 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
10 |
MTDayCell *cell = [tableView dequeueReusableCellWithIdentifier:DayCell forIndexPath:indexPath]; |
11 |
|
12 |
// Fetch Data
|
13 |
NSDictionary *data = [self.forecast objectAtIndex:indexPath.row]; |
14 |
|
15 |
// Initialize Date Formatter
|
16 |
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; |
17 |
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; |
18 |
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@"time"] doubleValue]]; |
19 |
|
20 |
// Configure Cell
|
21 |
[dateFormatter setDateFormat:@"EEE"]; |
22 |
[cell.labelDay setText:[dateFormatter stringFromDate:date]]; |
23 |
|
24 |
[dateFormatter setDateFormat:@"d"]; |
25 |
[cell.labelDate setText:[dateFormatter stringFromDate:date]]; |
26 |
|
27 |
float tempMin = [data[@"temperatureMin"] floatValue]; |
28 |
float tempMax = [data[@"temperatureMax"] floatValue]; |
29 |
[cell.labelTemp setText:[NSString stringWithFormat:@"%.0f°/%.0f°", tempMin, tempMax]]; |
30 |
|
31 |
[cell.labelWind setText:[NSString stringWithFormat:@"%.0f", [data[@"windSpeed"] floatValue]]]; |
32 |
|
33 |
float rainProbability = 0.0; |
34 |
if (data[@"precipProbability"]) { |
35 |
rainProbability = [data[@"precipProbability"] floatValue] * 100.0; |
36 |
}
|
37 |
|
38 |
[cell.labelRain setText:[NSString stringWithFormat:@"%.0f", rainProbability]]; |
39 |
|
40 |
return cell; |
41 |
}
|
1 |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { |
2 |
return 80.0; |
3 |
}
|
Damit dies alles funktioniert, müssen wir (1) die Header-Datei der MTDayCell-Klasse importieren, (2) eine statische Zeichenfolgenkonstante für die Zellwiederverwendungskennung deklarieren und (3) die MTDayCell als Klasse für diese Zellwiederverwendungskennung registrieren in viewDidLoad. In updateView laden wir die Tabellenansicht neu.
1 |
#import "MTDayCell.h"
|
1 |
static NSString *DayCell = @"DayCell"; |
1 |
- (void)viewDidLoad { |
2 |
[super viewDidLoad]; |
3 |
|
4 |
// Configure Table View
|
5 |
[self.tableView registerClass:[MTDayCell class] forCellReuseIdentifier:DayCell]; |
6 |
}
|
1 |
- (void)updateView { |
2 |
// Reload Table View
|
3 |
[self.tableView reloadData]; |
4 |
}
|
4. Benutzeroberfläche Rechte Ansicht
Schritt 1: Aktualisieren der Benutzeroberfläche
Es ist klar, dass wir einige wesentliche Änderungen am Location View Controller vornehmen müssen, um Chris 'Design zu implementieren. Die Tabellenansicht des Standort-View-Controllers enthält zwei Abschnitte anstelle von einem. Im oberen Bereich werden die gespeicherten Speicherorte angezeigt, während im unteren Bereich die Temperatureinstellung reserviert ist. Öffnen Sie zunächst MTLocationsViewController.xib und entfernen Sie die Navigationsleiste, die wir im vorherigen Artikel hinzugefügt haben (Abbildung 9). Dies bedeutet, dass wir auch den Ausgang für die Schaltfläche Bearbeiten in MTLocationsViewController.h und die Aktion editLocations in MTLocationsViewController.m löschen können.



Schritt 2: Erstellen der Standortzelle
Die Zellen, in denen die Positionen angezeigt werden, haben links eine Schaltfläche zum Löschen. Damit dies funktioniert, müssen wir eine benutzerdefinierte Tabellenansichtszelle erstellen. Erstellen Sie eine weitere UITableViewCell-Unterklasse und nennen Sie sie MTLocationCell (Abbildung 10). Öffnen Sie MTLocationCell.h und erstellen Sie zwei Eigenschaften: (1) buttonDelete (UIButton) und (2) labelLocation (UILabel). Wie Sie sehen, ist die Implementierung von MTLocationCell weniger komplex als die von MTHourCell und MTDayCell.



1 |
#import <UIKit/UIKit.h>
|
2 |
|
3 |
@interface MTLocationCell : UITableViewCell |
4 |
|
5 |
@property (strong, nonatomic) UIButton *buttonDelete; |
6 |
@property (strong, nonatomic) UILabel *labelLocation; |
7 |
|
8 |
@end
|
1 |
#import "MTLocationCell.h"
|
2 |
|
3 |
#define kMTButtonDeleteWidth 44.0
|
4 |
|
5 |
#define kMTLabelLocationMarginLeft 44.0
|
6 |
|
7 |
@implementation MTLocationCell |
8 |
|
9 |
#pragma mark -
|
10 |
#pragma mark Initialization
|
11 |
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { |
12 |
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; |
13 |
|
14 |
if (self) { |
15 |
// Helpers
|
16 |
CGSize size = self.contentView.frame.size; |
17 |
|
18 |
// Initialize Delete Button
|
19 |
self.buttonDelete = [UIButton buttonWithType:UIButtonTypeCustom]; |
20 |
|
21 |
// Configure Delete Button
|
22 |
[self.buttonDelete setFrame:CGRectMake(0.0, 0.0, kMTButtonDeleteWidth, size.height)]; |
23 |
[self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateNormal]; |
24 |
[self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateSelected]; |
25 |
[self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateDisabled]; |
26 |
[self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateHighlighted]; |
27 |
[self.buttonDelete setAutoresizingMask:(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin)]; |
28 |
[self.contentView addSubview:self.buttonDelete]; |
29 |
|
30 |
// Initialize Location Label
|
31 |
self.labelLocation = [[UILabel alloc] initWithFrame:CGRectMake(kMTLabelLocationMarginLeft, 0.0, size.width - kMTLabelLocationMarginLeft, size.height)]; |
32 |
|
33 |
// Configure Text Label
|
34 |
[self.labelLocation setTextColor:kMTColorGray]; |
35 |
[self.labelLocation setBackgroundColor:[UIColor clearColor]]; |
36 |
[self.labelLocation setFont:[UIFont fontWithName:@"GillSans" size:20.0]]; |
37 |
[self.labelLocation setAutoresizingMask:(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin)]; |
38 |
[self.contentView addSubview:self.labelLocation]; |
39 |
}
|
40 |
|
41 |
return self; |
42 |
}
|
43 |
|
44 |
@end
|
Schritt 3: Aktualisieren des Tabellenquellen-Datenquellenprotokolls
Um das Design zu implementieren, müssen wir die Protokolle UITableViewDataSource und UITableViewDelegate aktualisieren. Importieren Sie zunächst die Header-Datei der MTLocationCell-Klasse und die zuvor erstellte Kategorie in NSUserDefaults. Die Tabellenansicht enthält drei Zelltypen, und wir müssen für jeden Typ einen Wiederverwendungsbezeichner deklarieren (sehen Sie unten).
1 |
#import "MTLocationsViewController.h"
|
2 |
|
3 |
#import "MTLocationCell.h"
|
4 |
#import "NSUserDefaults+Helpers.h"
|
5 |
|
6 |
@interface MTLocationsViewController () |
7 |
|
8 |
@property (strong, nonatomic) NSMutableArray *locations; |
9 |
|
10 |
@end
|
1 |
static NSString *AddLocationCell = @"AddLocationCell"; |
2 |
static NSString *LocationCell = @"LocationCell"; |
3 |
static NSString *SettingsCell = @"SettingsCell"; |
In setupView konfigurieren wir die Tabellenansicht, indem wir (1) die Eigenschaft separatorStyle auf UITableViewCellSeparatorStyleNone setzen und( ) eine Klasse für jede Wiederverwendungskennung registrieren.
1 |
- (void)setupView { |
2 |
// Setup Table View
|
3 |
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; |
4 |
|
5 |
// Register Class for Cell Reuse
|
6 |
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:AddLocationCell]; |
7 |
[self.tableView registerClass:[MTLocationCell class] forCellReuseIdentifier:LocationCell]; |
8 |
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:SettingsCell]; |
9 |
}
|
Das UITableViewDataSource-Protokoll ändert sich erheblich, und die Implementierung der verschiedenen Methoden kann zunächst entmutigend erscheinen. Der größte Teil der Komplexität ist jedoch auf verschachtelte if-Anweisungen zurückzuführen.
1 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { |
2 |
return 2; |
3 |
}
|
4 |
|
5 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { |
6 |
if (section == 0) { |
7 |
return [self.locations count] + 1; |
8 |
}
|
9 |
|
10 |
return 2; |
11 |
}
|
Wir implementieren auch tableView:titleForHeaderInSection: und tableView:viewForHeaderInSection:, um die Standardabschnittsüberschriften durch ein benutzerdefiniertes Design zu ersetzen, das dem Design der Anwendung entspricht. Bei der Implementierung von tableView:viewForHeaderInSection: ist es wichtig, auch tableView:heightForHeaderInSection: zu implementieren.
1 |
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { |
2 |
switch (section) { |
3 |
case 0: { |
4 |
return NSLocalizedString(@"Locations", nil); |
5 |
break; |
6 |
}
|
7 |
default: { |
8 |
return NSLocalizedString(@"Temperature", nil); |
9 |
break; |
10 |
}
|
11 |
}
|
12 |
|
13 |
return nil; |
14 |
}
|
15 |
|
16 |
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { |
17 |
// Header Text
|
18 |
NSString *text = [self tableView:tableView titleForHeaderInSection:section]; |
19 |
|
20 |
// Helpers
|
21 |
CGRect labelFrame = CGRectMake(12.0, 0.0, tableView.bounds.size.width, 44.0); |
22 |
|
23 |
// Initialize Label
|
24 |
UILabel *label = [[UILabel alloc] initWithFrame:labelFrame]; |
25 |
|
26 |
// Configure Label
|
27 |
[label setText:text]; |
28 |
[label setTextColor:kMTColorOrange]; |
29 |
[label setFont:[UIFont fontWithName:@"GillSans" size:20.0]]; |
30 |
[label setBackgroundColor:[UIColor clearColor]]; |
31 |
|
32 |
// Initialize View
|
33 |
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, tableView.bounds.size.width, 34.0)]; |
34 |
[backgroundView setBackgroundColor:[UIColor clearColor]]; |
35 |
[backgroundView addSubview:label]; |
36 |
|
37 |
return backgroundView; |
38 |
}
|
39 |
|
40 |
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { |
41 |
return 40.0; |
42 |
}
|
Wir verwenden tableView:heightForFooterInSection:, um Leerzeichen zwischen dem oberen und unteren Bereich zu erstellen. Dies bedeutet, dass wir auch tableView:viewForFooterInSection: implementieren müssen.
1 |
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { |
2 |
// Initialize View
|
3 |
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, tableView.bounds.size.width, 34.0)]; |
4 |
[backgroundView setBackgroundColor:[UIColor whiteColor]]; |
5 |
|
6 |
return backgroundView; |
7 |
}
|
8 |
|
9 |
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { |
10 |
if (section == 0) { |
11 |
return 40.0; |
12 |
}
|
13 |
|
14 |
return 0.0; |
15 |
}
|
Die Implementierung von tableView:cellForRowAtIndexPath: ist aufgrund der drei Zelltypen in der Tabellenansicht etwas komplexer.
1 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
2 |
UITableViewCell *cell = nil; |
3 |
|
4 |
if (indexPath.section == 0) { |
5 |
if (indexPath.row == 0) { |
6 |
cell = [tableView dequeueReusableCellWithIdentifier:AddLocationCell forIndexPath:indexPath]; |
7 |
} else { |
8 |
cell = [tableView dequeueReusableCellWithIdentifier:LocationCell forIndexPath:indexPath]; |
9 |
}
|
10 |
|
11 |
} else { |
12 |
cell = [tableView dequeueReusableCellWithIdentifier:SettingsCell forIndexPath:indexPath]; |
13 |
}
|
14 |
|
15 |
// Configure Cell
|
16 |
[self configureCell:cell atIndexPath:indexPath]; |
17 |
|
18 |
return cell; |
19 |
}
|
In configureCell:atIndexPath: konfigurieren wir jede Zelle. Wie ich bereits geschrieben habe, ist die Komplexität hauptsächlich auf verschachtelte if-Anweisungen zurückzuführen. Durch Tippen auf die Schaltfläche Löschen in einer Standortzelle wird die Nachricht deleteLocation: an den Standort-View-Controller gesendet. Wir werden deleteLocation: in Kürze implementieren.
1 |
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { |
2 |
// Helpers
|
3 |
UIFont *fontLight = [UIFont fontWithName:@"GillSans-Light" size:18.0]; |
4 |
UIFont *fontRegular = [UIFont fontWithName:@"GillSans" size:18.0]; |
5 |
|
6 |
// Background View Image
|
7 |
UIImage *backgroundImage = [[UIImage imageNamed:@"background-location-cell"] resizableImageWithCapInsets:UIEdgeInsetsMake(10.0, 0.0, 0.0, 10.0)]; |
8 |
|
9 |
// Configure Table View Cell
|
10 |
[cell.textLabel setFont:fontLight]; |
11 |
[cell.textLabel setTextColor:kMTColorGray]; |
12 |
[cell.textLabel setBackgroundColor:[UIColor clearColor]]; |
13 |
[cell setSelectionStyle:UITableViewCellSelectionStyleNone]; |
14 |
|
15 |
if (indexPath.section == 0) { |
16 |
if (indexPath.row == 0) { |
17 |
[cell.textLabel setText:@"Add Current Location"]; |
18 |
[cell.imageView setContentMode:UIViewContentModeCenter]; |
19 |
[cell.imageView setImage:[UIImage imageNamed:@"icon-add-location"]]; |
20 |
|
21 |
// Background View Image
|
22 |
backgroundImage = [[UIImage imageNamed:@"background-add-location-cell"] resizableImageWithCapInsets:UIEdgeInsetsMake(10.0, 0.0, 0.0, 10.0)]; |
23 |
|
24 |
} else { |
25 |
// Fetch Location
|
26 |
NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)]; |
27 |
|
28 |
// Configure Cell
|
29 |
[[(MTLocationCell *)cell buttonDelete] addTarget:self action:@selector(deleteLocation:) forControlEvents:UIControlEventTouchUpInside]; |
30 |
[[(MTLocationCell *)cell labelLocation] setText:[NSString stringWithFormat:@"%@, %@", location[MTLocationKeyCity], location[MTLocationKeyCountry]]]; |
31 |
}
|
32 |
|
33 |
} else { |
34 |
if (indexPath.row == 0) { |
35 |
[cell.textLabel setText:NSLocalizedString(@"Fahrenheit", nil)]; |
36 |
|
37 |
if ([NSUserDefaults isDefaultCelcius]) { |
38 |
[cell.textLabel setFont:fontLight]; |
39 |
[cell.textLabel setTextColor:kMTColorGray]; |
40 |
} else { |
41 |
[cell.textLabel setFont:fontRegular]; |
42 |
[cell.textLabel setTextColor:kMTColorGreen]; |
43 |
}
|
44 |
|
45 |
} else { |
46 |
[cell.textLabel setText:NSLocalizedString(@"Celsius", nil)]; |
47 |
|
48 |
if ([NSUserDefaults isDefaultCelcius]) { |
49 |
[cell.textLabel setFont:fontRegular]; |
50 |
[cell.textLabel setTextColor:kMTColorGreen]; |
51 |
} else { |
52 |
[cell.textLabel setFont:fontLight]; |
53 |
[cell.textLabel setTextColor:kMTColorGray]; |
54 |
}
|
55 |
}
|
56 |
}
|
57 |
|
58 |
if (backgroundImage) { |
59 |
// Background View
|
60 |
UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, cell.frame.size.width, cell.frame.size.height)]; |
61 |
[backgroundView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; |
62 |
[backgroundView setImage:backgroundImage]; |
63 |
[cell setBackgroundView:backgroundView]; |
64 |
}
|
65 |
}
|
Da Standorte jetzt durch Tippen auf die Schaltfläche Löschen in den Standortzellen gelöscht werden können, müssen die Zeilen selbst nicht mehr bearbeitet werden können. Dies bedeutet, dass die Implementierung von tableView:canEditRowAtIndexPath: auf die Rückgabe von NO reduziert werden kann und die Implementierung von tableView:commitEditingStyle:forRowAtIndexPath: vollständig entfernt werden kann.
1 |
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { |
2 |
return NO; |
3 |
}
|
Schritt 4: Aktualisieren des Tabellenansicht Delegierten-Protokolls
In tableView:didSelectRowAtIndexPath: fügen wir etwas mehr Komplexität hinzu, da der zweite Abschnitt mit der Temperatureinstellung enthalten ist. Dank unserer Kategorie zu NSUserDefaults ist die Implementierung einfach und übersichtlich.
1 |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { |
2 |
[tableView deselectRowAtIndexPath:indexPath animated:YES]; |
3 |
|
4 |
if (indexPath.section == 0) { |
5 |
if (indexPath.row == 0) { |
6 |
// Notify Delegate
|
7 |
[self.delegate controllerShouldAddCurrentLocation:self]; |
8 |
|
9 |
} else { |
10 |
// Fetch Location
|
11 |
NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)]; |
12 |
|
13 |
// Notify Delegate
|
14 |
[self.delegate controller:self didSelectLocation:location]; |
15 |
}
|
16 |
|
17 |
} else { |
18 |
if (indexPath.row == 0 && [NSUserDefaults isDefaultCelcius]) { |
19 |
[NSUserDefaults setDefaultToFahrenheit]; |
20 |
} else if (![NSUserDefaults isDefaultCelcius]) { |
21 |
[NSUserDefaults setDefaultToCelcius]; |
22 |
}
|
23 |
|
24 |
// Update Section
|
25 |
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone]; |
26 |
}
|
27 |
|
28 |
// Show Center View Controller
|
29 |
[self.viewDeckController closeLeftViewAnimated:YES]; |
30 |
}
|
Die Zeilen in Chris Design sind etwas höher als die Standardhöhe von 44 Punkten. Um dieses Detail in die Praxis umzusetzen, implementieren wir tableView:heightForRowAtIndexPath: wie unten gezeigt.
1 |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { |
2 |
return 50.0; |
3 |
}
|
Schritt 5: Löschen von Standorten (erneut besucht)
Als letztes müssen wir die Methode deleteLocation: implementieren, die aufgerufen wird, wenn die Schaltfläche delete in einer Standortzelle getippt wird. Die Implementierung ist ausführlicher als erwartet. Das Ableiten der Zeilennummer aus der Zelle, zu der die Schaltfläche zum Löschen gehört, ist nicht so trivial, wie es sein sollte. Sobald wir jedoch den Indexpfad der Zelle haben, zu der die Schaltfläche gehört, müssen wir nur das Array von Speicherorten aktualisieren, die Benutzerstandarddatenbank aktualisieren und die Tabellenansicht aktualisieren.
1 |
- (void)deleteLocation:(id)sender { |
2 |
UITableViewCell *cell = (UITableViewCell *)[[(UIButton *)sender superview] superview]; |
3 |
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell]; |
4 |
|
5 |
// Update Locations
|
6 |
[self.locations removeObjectAtIndex:(indexPath.row - 1)]; |
7 |
|
8 |
// Update User Defaults
|
9 |
[[NSUserDefaults standardUserDefaults] setObject:self.locations forKey:MTRainUserDefaultsLocations]; |
10 |
|
11 |
// Update Table View
|
12 |
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop]; |
13 |
}
|
5. Feinschliff
Um unser Projekt abzuschließen, müssen wir die Standard-Startbilder durch die von Chris Carey bereitgestellten ersetzen. Die Startbilder sind auch in den Quelldateien dieses Artikels enthalten.
Erstellen Sie die Anwendung und führen Sie sie aus, um das Endergebnis in Aktion zu sehen. Obwohl wir einige Zeit damit verbracht haben, die Anwendung zu erstellen und zu entwerfen, gibt es immer noch einige Ecken und Kanten. Es wäre auch schön, einen Caching-Mechanismus zu implementieren, damit wir dem Benutzer zwischengespeicherte Daten anzeigen können, solange eine Anforderung an die Vorhersage-API nicht zurückgegeben wurde. Dies sind einige Beispiele für Verbesserungen, die wir unserer Anwendung hinzufügen können.
Abschluss
Das Erstellen der Benutzeroberfläche war ein ziemlicher Arbeitsaufwand, aber der Code war nicht allzu kompliziert. Ich hoffe, dieses Tutorial hat Ihnen gezeigt, was ein großartiges Design für eine Anwendung leisten kann. Insbesondere Verbraucheranwendungen profitieren wirklich von einem ansprechenden, frischen Design, wie wir es in diesem Tutorial verwendet haben.



