В последней статье этой серии я покажу вам, как создать пользовательский интерфейс нашего погодного приложения. Благодаря работе Криса Кэри , постоянного участника Vectortuts + , у нас есть прекрасный дизайн, с которым мы можем работать!
Вступление
Для разработки нашего погодного приложения я сотрудничал с Крисом Кэри , постоянным участником Vectortuts + . Крис создал красивый дизайн, который мы можем использовать для создания пользовательского интерфейса приложения. После того как Крис передал мне дизайн в качестве векторной иллюстрации, я нарезал иллюстрацию и подготовил ее для использования в нашем проекте. Вы можете найти нарезанные изображения в исходных файлах этой статьи.
Из-за ограниченной возможности настройки UIKit нам придется пойти на несколько компромиссов при реализации дизайна Криса. Однако конечный результат будет очень похож на дизайн, который Крис создал для нас. Например, UISwitch
класса UISwitch UISwitch
очень ограничена, и поэтому мы будем использовать другой подход для реализации настройки температуры. Крис использовал два пользовательских шрифта для своего дизайна, Maven Pro и Mission Gothic. Хотя iOS поддерживает пользовательские шрифты, мы будем использовать только те шрифты, которые доступны на iOS.
1. Уведомления
Шаг 1: Создание строковых констант
Всякий раз, когда контроллер представления погоды получает данные о погоде от API прогноза, его собственный вид и правильный вид должны быть обновлены. Это означает, что нам нужно уведомить контроллер представления прогноза и отправить ему данные о погоде. Существует несколько способов уведомить контроллер представления прогноза о таком событии. Публикация уведомлений с помощью NSNotificationCenter
является самым простым решением и предоставляет нам максимальную гибкость. Мы можем использовать то же решение для обновления пользовательского интерфейса после того, как пользователь переключил настройку температуры. Отправляя уведомление, каждый объект, который заинтересован в этом событии, может зарегистрироваться в качестве наблюдателя. Я обновил MTConstants.h / .m, чтобы создать строковую константу для каждого уведомления.
1
2
|
extern NSString * const MTRainWeatherDataDidChangeChangeNotification;
extern NSString * const MTRainTemperatureUnitDidChangeNotification;
|
1
2
|
NSString * const MTRainWeatherDataDidChangeChangeNotification = @»com.mobileTuts.MTRainWeatherDataDidChangeChangeNotification»;
NSString * const MTRainTemperatureUnitDidChangeNotification = @»com.mobileTuts.MTRainTemperatureUnitDidChangeNotification»;
|
Шаг 2: Диспетчер погоды
После получения ответа от Forecast API нам нужно сохранить его, чтобы мы могли использовать его позже для обновления представления. Создайте два частных свойства в MTWeatherViewController.m , (1) response
(типа NSDictionary
), который будет содержать ответ API прогноза и (2) forecast
(типа NSArray
), который будет содержать подмножество ответа, данные о погоде для следующие часы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
#import «MTWeatherViewController.h»
#import «MTForecastClient.h»
@interface MTWeatherViewController () <CLLocationManagerDelegate> {
BOOL _locationFound;
}
@property (strong, nonatomic) NSDictionary *location;
@property (strong, nonatomic) NSDictionary *response;
@property (strong, nonatomic) NSArray *forecast;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end
|
Чтобы получать уведомления, нам нужно обновить initWithNibName:bundle:
как показано ниже ( MTWeatherViewController.m ). В weatherDataDidChangeChange:
мы храним ответ API прогноза в response
и forecast
и обновляем представление контроллера представления. В temperatureUnitDidChange
нам нужно только обновить представление, чтобы отразить измененную настройку.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
— (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialize Location Manager
self.locationManager = [[CLLocationManager alloc] init];
// Configure Location Manager
[self.locationManager setDelegate:self];
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
// Add Observer
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[nc addObserver:self selector:@selector(reachabilityStatusDidChange:) name:MTRainReachabilityStatusDidChangeNotification object:nil];
[nc addObserver:self selector:@selector(weatherDataDidChangeChange:) name:MTRainWeatherDataDidChangeChangeNotification object:nil];
[nc addObserver:self selector:@selector(temperatureUnitDidChange:) name:MTRainTemperatureUnitDidChangeNotification object:nil];
}
return self;
}
|
1
2
3
4
5
6
7
8
|
— (void)weatherDataDidChangeChange:(NSNotification *)notification {
// Update Response & Forecast
[self setResponse:[notification userInfo]];
[self setForecast:self.response[@»hourly»][@»data»]];
// Update View
[self updateView];
}
|
1
2
3
4
|
— (void)temperatureUnitDidChange:(NSNotification *)notification {
// Update View
[self updateView];
}
|
Шаг 3: Диспетчер просмотра прогноза
Шаги практически идентичны в классе MTForecastViewController
. Мы обновляем initWithNibName:bundle:
как показано ниже, создаем два свойства ( response
и forecast
) и реализуем weatherDataDidChangeChange:
и weatherDataDidChangeChange:
Разница заключается в данных о погоде, сохраненных в forecast
. Мы реализуем updateView
чуть позже в этом руководстве, но рекомендуется создать реализацию-заглушку, чтобы избавиться от предупреждений компилятора.
01
02
03
04
05
06
07
08
09
10
11
12
|
— (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Add Observer
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(weatherDataDidChangeChange:) name:MTRainWeatherDataDidChangeChangeNotification object:nil];
[nc addObserver:self selector:@selector(temperatureUnitDidChange:) name:MTRainTemperatureUnitDidChangeNotification object:nil];
}
return self;
}
|
1
2
3
4
5
6
7
8
|
#import «MTForecastViewController.h»
@interface MTForecastViewController ()
@property (strong, nonatomic) NSDictionary *response;
@property (strong, nonatomic) NSArray *forecast;
@end
|
1
2
3
4
5
6
7
8
|
— (void)weatherDataDidChangeChange:(NSNotification *)notification {
// Update Response & Forecast
[self setResponse:[notification userInfo]];
[self setForecast:self.response[@»daily»][@»data»]];
// Update View
[self updateView];
}
|
1
2
3
4
|
— (void)temperatureUnitDidChange:(NSNotification *)notification {
// Update View
[self updateView];
}
|
1
2
3
|
— (void)updateView {
}
|
2. Интерфейс пользователя интерфейса просмотра
Шаг 1: Аутлеты и Действия
Несмотря на то, что центральное представление содержит много информации, это не так сложно реализовать. Давайте начнем с создания ряда торговых точек и одного нового действия. Обновите MTWeatherViewController.h, как показано ниже. Пересмотрите дизайн Криса, чтобы лучше понять расположение и назначение каждого элемента пользовательского интерфейса. Разница с дизайном Криса состоит в том, что мы заменим значок календаря в правом верхнем углу на кнопку обновления, которую мы создали в предыдущем уроке. Данные о погоде в течение следующих часов будут представлены в виде сбора, что означает, что класс MTWeatherViewController
должен соответствовать UICollectionViewDataSource
, UICollectionViewDelegate
и UICollectionViewDelegateFlowLayout
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
#import <UIKit/UIKit.h>
#import «MTLocationsViewController.h»
@interface MTWeatherViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, MTLocationsViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UIButton *buttonLocation;
@property (weak, nonatomic) IBOutlet UIButton *buttonRefresh;
@property (weak, nonatomic) IBOutlet UILabel *labelDate;
@property (weak, nonatomic) IBOutlet UILabel *labelTemp;
@property (weak, nonatomic) IBOutlet UILabel *labelTime;
@property (weak, nonatomic) IBOutlet UILabel *labelWind;
@property (weak, nonatomic) IBOutlet UILabel *labelRain;
@property (weak, nonatomic) IBOutlet UILabel *labelLocation;
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@end
|
Нам также нужно добавить действие для кнопки местоположения в левом верхнем углу. Откройте MTWeatherViewController.m и добавьте новое действие с именем openLeftView:
В openLeftView:
мы сообщаем контроллеру openLeftView:
представления контроллера вида переключать левое представление. Помните, что также можно провести пальцем слева направо, чтобы открыть вид слева.
1
2
3
|
— (IBAction)openLeftView:(id)sender {
[self.viewDeckController toggleLeftViewAnimated:YES];
}
|
Шаг 2: Создание пользовательского интерфейса
Откройте MTWeatherViewController.xib и создайте пользовательский интерфейс, как показано на рисунке 2. При создании пользовательского интерфейса важно убедиться, что пользовательский интерфейс отображается правильно как на 3,5- дюймовом экране, так и на 4- дюймовом экране iPhone 5. Вы можете проверить это, выбрав представление контроллера представления и изменив атрибут Size в Инспекторе Атрибутов . Чтобы достичь желаемого результата, вам нужно настроить ограничения на автоматическое расположение элементов пользовательского интерфейса. Цель состоит в том, чтобы данные о погоде прилипали к верхней части представления, а сводный вид приклеивается к нижней части. Значки рядом с метками времени, ветра и дождя являются экземплярами UIImageView
.
Сконфигурируйте метки и кнопки, как показано на рисунке 2. Это включает в себя правильное выравнивание текста меток, установку типов обеих кнопок на Custom , назначение Владельцу файла dataSource
delegate
dataSource
и delegate
, а также установку направления прокрутки представления собрания. макет потока в горизонтальном положении. Я фанат Gill Sans, поэтому этот шрифт я выбрал для этого проекта. Прежде чем вернуться к файлу реализации контроллера представления погоды, подключите розетки и действие, которое мы создали ранее. В дополнение к меткам и кнопкам я также добавил представление изображения в представление контроллера представления для отображения фонового изображения.
Как я уже упоминал во введении, вы можете найти обложку приложения в исходных файлах этого руководства. Создайте папку с именем Artwork в вашем проекте Xcode и перетащите обложку в эту папку.
Шаг 3: Заполнение пользовательского интерфейса
В настоящее время мы регистрируем ответ API Forecast на консоль Xcode. Чтобы начать использовать данные о погоде, нам нужно обновить метод fetchWeatherData
как показано ниже. В блоке завершения requestWeatherForCoordinate:completion:
мы скрываем HUD хода выполнения и отправляем уведомление в основной поток. Мы используем функцию dispatch_async
и передаем очередь основного потока в качестве первого аргумента. Словарь userInfo
уведомления является ответом на запрос.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
— (void)fetchWeatherData {
if ([[MTForecastClient sharedClient] networkReachabilityStatus] == AFNetworkReachabilityStatusNotReachable) return;
// Show Progress HUD
[SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
// Query Forecast API
double lat = [[_location objectForKey:MTLocationKeyLatitude] doubleValue];
double lng = [[_location objectForKey:MTLocationKeyLongitude] doubleValue];
[[MTForecastClient sharedClient] requestWeatherForCoordinate:CLLocationCoordinate2DMake(lat, lng) completion:^(BOOL success, NSDictionary *response) {
// Dismiss Progress HUD
[SVProgressHUD dismiss];
if (response && [response isKindOfClass:[NSDictionary class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// Post Notification on Main Thread
NSNotification *notification = [NSNotification notificationWithName:MTRainWeatherDataDidChangeChangeNotification object:nil userInfo:response];
[[NSNotificationCenter defaultCenter] postNotification:notification];
});
}
}];
}
|
Контроллеры представления погоды и прогноза являются наблюдателями уведомлений MTRainWeatherDataDidChangeChangeNotification
. Контроллер представления погоды вызывает weatherDataDidChangeChange:
который, в свою очередь, вызывает updateView
. В updateView
мы вызываем updateCurrentWeather
и обновляем представление коллекции, отправляя ему сообщение reloadData
.
01
02
03
04
05
06
07
08
09
10
|
— (void)updateView {
// Update Location Label
[self.labelLocation setText:[self.location objectForKey:MTLocationKeyCity]];
// Update Current Weather
[self updateCurrentWeather];
// Reload Collection View
[self.collectionView reloadData];
}
|
Прежде чем мы реализуем updateCurrentWeather
, я хочу сделать небольшой обход. Мы будем отображать значения температуры в разных местах приложения, и поскольку мы поддерживаем Фаренгейт и Цельсий, это может стать громоздким. Поэтому полезно создать класс, который централизует эту логику, чтобы нам не нужно было загромождать нашу базу кода операторами if и преобразованиями температуры.
Шаг 4: Создание класса настроек
Прежде чем мы создадим класс, который обрабатывает преобразование температуры, мы должны иметь возможность сохранить текущую настройку температуры в базе данных пользователя по умолчанию. Повторно посетите MTConstants.h / .m и объявите строковую константу с именем MTRainUserDefaultsTemperatureUnit
.
1
|
extern NSString * const MTRainUserDefaultsTemperatureUnit;
|
1
|
NSString * const MTRainUserDefaultsTemperatureUnit = @»temperatureUnit»;
|
Чтобы упростить работу с настройками, я часто создаю категорию в NSUserDefaults
которая позволяет мне быстро и элегантно получать доступ к настройкам приложения. Позвольте мне показать вам, что я имею в виду. Создайте новую категорию Objective-C (рисунок 3), назовите категорию Helpers и сделайте ее категорией в NSUserDefaults
(рисунок 4). В NSUserDefaults + Helpers.h мы объявляем три метода класса, как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
|
#import <Foundation/Foundation.h>
@interface NSUserDefaults (Helpers)
#pragma mark —
#pragma mark Temperature
+ (BOOL)isDefaultCelcius;
+ (void)setDefaultToCelcius;
+ (void)setDefaultToFahrenheit;
@end
|
Хотя эти методы не волшебны, они очень полезны. Первый метод, isDefaultCelcius
, сообщает нам, установлена ли единица измерения температуры по Цельсию или нет. Два других метода позволяют легко переключаться между градусами Фаренгейта и Цельсия. Мы не только обновляем базу данных пользователей по умолчанию, но и публикуем уведомление, информирующее наблюдателей об изменениях.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
+ (BOOL)isDefaultCelcius {
return [[NSUserDefaults standardUserDefaults] integerForKey:MTRainUserDefaultsTemperatureUnit] == 1;
}
+ (void)setDefaultToCelcius {
// Update User Defaults
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setInteger:1 forKey:MTRainUserDefaultsTemperatureUnit];
[ud synchronize];
// Post Notification
[[NSNotificationCenter defaultCenter] postNotificationName:MTRainTemperatureUnitDidChangeNotification object:nil];
}
+ (void)setDefaultToFahrenheit {
// Update User Defaults
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setInteger:0 forKey:MTRainUserDefaultsTemperatureUnit];
[ud synchronize];
// Post Notification
[[NSNotificationCenter defaultCenter] postNotificationName:MTRainTemperatureUnitDidChangeNotification object:nil];
}
|
Настало время создать класс настроек, о котором я писал ранее. Решение на удивление легко. Создайте новый класс Objective-C, назовите его MTSettings
и сделайте его подклассом NSObject
(рисунок 5). В MTSettings.h мы объявляем один метод класса, formatTemperature:
В MTSettings.m мы импортируем заголовок категории, которую мы создали недавно, и реализуем formatTemperature:
как показано ниже. Метод принимает экземпляр NSNumber
, преобразует его в число с плавающей точкой и возвращает отформатированную строку, основанную на настройке температуры.
1
2
3
4
5
6
7
8
9
|
#import <Foundation/Foundation.h>
@interface MTSettings : NSObject
#pragma mark —
#pragma mark Convenience Methods
+ (NSString *)formatTemperature:(NSNumber *)temperature;
@end
|
1
|
#import «NSUserDefaults+Helpers.h»
|
1
2
3
4
5
6
7
8
9
|
+ (NSString *)formatTemperature:(NSNumber *)temperature {
float value = [temperature floatValue];
if ([NSUserDefaults isDefaultCelcius]) {
value = (value — 32.0) * (5.0 / 9.0);
}
return [NSString stringWithFormat:@»%.0f°», value];
}
|
Прежде чем мы продолжим, добавьте оператор импорта для класса MTSettings
в предварительно скомпилированный заголовочный файл проекта, чтобы мы могли использовать его на протяжении всего проекта.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning «This project uses features only available in iOS SDK 3.0 and later.»
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreLocation/CoreLocation.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import «AFNetworking.h»
#import «SVProgressHUD.h»
#import «IIViewDeckController.h»
#import «MTSettings.h»
#import «MTConstants.h»
#endif
|
Теперь updateCurrentWeather
время реализовать updateCurrentWeather
в классе MTWeatherViewController
. Данные для текущей погоды являются подмножеством ответа, который мы получили от API прогноза. Реализация updateCurrentWeather
довольно проста. Единственное предостережение, на которое стоит обратить внимание, — это вероятность осадков. Если это значение равно 0
, ключевой параметр precipProbability
не включается в ответ. По этой причине мы сначала проверяем наличие ключа precipProbability
в словаре ответов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
— (void)updateCurrentWeather {
// Weather Data
NSDictionary *data = [self.response objectForKey:@»currently»];
// Update Date Label
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[dateFormatter setDateFormat:@»EEEE, MMM d»];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@»time»] doubleValue]];
[self.labelDate setText:[dateFormatter stringFromDate:date]];
// Update Temperature Label
[self.labelTemp setText:[MTSettings formatTemperature:data[@»temperature»]]];
// Update Time Label
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
[timeFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[timeFormatter setDateFormat:@»ha»];
[self.labelTime setText:[timeFormatter stringFromDate:[NSDate date]]];
// Update Wind Label
[self.labelWind setText:[NSString stringWithFormat:@»%.0fMP», [data[@»windSpeed»] floatValue]]];
// Update Rain Label
float rainProbability = 0.0;
if (data[@»precipProbability»]) {
rainProbability = [data[@»precipProbability»] floatValue] * 100.0;
}
[self.labelRain setText:[NSString stringWithFormat:@»%.0f%%», rainProbability]];
}
|
Шаг 5: Заполнение представления коллекции
Чтобы заполнить представление коллекции, нам сначала нужно создать подкласс UICollectionViewCell
. Создайте новый класс Objective C, назовите его MTHourCell
и сделайте его подклассом UICollectionViewCell
(рисунок 6). Создание пользовательских ячеек таблицы или коллекции может быть трудоемким и кропотливым. Если вы хотите больше узнать о создании пользовательских ячеек таблиц и представлений коллекций, то я предлагаю вам взглянуть на учебник, который я написал несколько недель назад.
В интерфейсе MTHourCell
мы объявляем четыре свойства типа UILabel
. Мы не делаем много магии в MTHourcell.m, как вы можете видеть ниже. Чтобы лучше понять метод initWithFrame:
вернитесь к дизайну, который я показал вам в начале этой статьи. Я не буду обсуждать реализацию initWithFrame:
подробно, но хочу отметить, что я использую определение препроцессора для цвета текста надписей. Я добавил определение препроцесса в MTConstants.h, чтобы сделать его доступным для всего проекта (см. Ниже).
01
02
03
04
05
06
07
08
09
10
|
#import <UIKit/UIKit.h>
@interface MTHourCell : UICollectionViewCell
@property (strong, nonatomic) UILabel *labelTime;
@property (strong, nonatomic) UILabel *labelTemp;
@property (strong, nonatomic) UILabel *labelWind;
@property (strong, nonatomic) UILabel *labelRain;
@end
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
#import «MTHourCell.h»
#define kMTLabelBottomWidth 40.0
#define kMTLabelBottomHeight 40.0
@interface MTHourCell ()
@end
@implementation MTHourCell
— (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Helpers
CGSize size = self.contentView.frame.size;
// Initialize Label Time
self.labelTime = [[UILabel alloc] initWithFrame:CGRectMake(30.0, 0.0, 50.0, 40.0)];
// Configure Label Time
[self.labelTime setBackgroundColor:[UIColor clearColor]];
[self.labelTime setTextColor:[UIColor whiteColor]];
[self.labelTime setFont:[UIFont fontWithName:@»GillSans-Light» size:18.0]];
[self.labelTime setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin)];
[self.contentView addSubview:self.labelTime];
// Initialize Label Temp
self.labelTemp = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 46.0, 80.0, 44.0)];
// Configure Label Temp
[self.labelTemp setBackgroundColor:[UIColor clearColor]];
[self.labelTemp setTextAlignment:NSTextAlignmentCenter];
[self.labelTemp setTextColor:kMTColorGray];
[self.labelTemp setFont:[UIFont fontWithName:@»GillSans-Bold» size:40.0]];
[self.labelTemp setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin)];
[self.contentView addSubview:self.labelTemp];
// Initialize Label Wind
self.labelWind = [[UILabel alloc] initWithFrame:CGRectMake(0.0, size.height — kMTLabelBottomHeight, kMTLabelBottomWidth, kMTLabelBottomHeight)];
// Configure Label Wind
[self.labelWind setBackgroundColor:[UIColor clearColor]];
[self.labelWind setTextAlignment:NSTextAlignmentCenter];
[self.labelWind setTextColor:kMTColorGray];
[self.labelWind setFont:[UIFont fontWithName:@»GillSans-Light» size:16.0]];
[self.labelWind setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)];
[self.contentView addSubview:self.labelWind];
// Initialize Label Rain
self.labelRain = [[UILabel alloc] initWithFrame:CGRectMake(size.width — kMTLabelBottomWidth, size.height — kMTLabelBottomHeight, kMTLabelBottomWidth, kMTLabelBottomHeight)];
// Configure Label Rain
[self.labelRain setBackgroundColor:[UIColor clearColor]];
[self.labelRain setTextAlignment:NSTextAlignmentCenter];
[self.labelRain setTextColor:kMTColorGray];
[self.labelRain setFont:[UIFont fontWithName:@»GillSans-Light» size:16.0]];
[self.labelRain setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)];
[self.contentView addSubview:self.labelRain];
// Background View
UIImage *backgroundImage = [[UIImage imageNamed:@»background-hour-cell»] resizableImageWithCapInsets:UIEdgeInsetsMake(40.0, 10.0, 10.0, 10.0)];
UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
[backgroundView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[backgroundView setImage:backgroundImage];
[self setBackgroundView:backgroundView];
}
return self;
}
@end
|
1
2
3
|
#define kMTColorGray [UIColor colorWithRed:0.737 green:0.737 blue:0.737 alpha:1.0]
#define kMTColorGreen [UIColor colorWithRed:0.325 green:0.573 blue:0.388 alpha:1.0]
#define kMTColorOrange [UIColor colorWithRed:1.000 green:0.306 blue:0.373 alpha:1.0]
|
Как вы можете видеть ниже, реализация UICollectionViewDataSource
, UICollectionViewDelegate
и UICollectionViewDelegateFlowLayout
очень похожа на реализацию протоколов UITableViewDataSource
и UITableViewDelegate
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
— (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return self.forecast ?
}
— (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.forecast count];
}
— (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MTHourCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:HourCell forIndexPath:indexPath];
// Fetch Data
NSDictionary *data = [self.forecast objectAtIndex:indexPath.row];
// Initialize Date Formatter
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
[timeFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[timeFormatter setDateFormat:@»ha»];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@»time»] doubleValue]];
// Configure Cell
[cell.labelTime setText:[timeFormatter stringFromDate:date]];
[cell.labelTemp setText:[MTSettings formatTemperature:data[@»temperature»]]];
[cell.labelWind setText:[NSString stringWithFormat:@»%.0fMP», [data[@»windSpeed»] floatValue]]];
float rainProbability = 0.0;
if (data[@»precipProbability»]) {
rainProbability = [data[@»precipProbability»] floatValue] * 100.0;
}
[cell.labelRain setText:[NSString stringWithFormat:@»%.0f%%», rainProbability]];
return cell;
}
|
1
2
3
4
5
6
7
|
— (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(80.0, 120.0);
}
— (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0);
}
|
Чтобы все это работало, нам нужно (1) импортировать файл заголовка MTHourCell
, (2) объявить статическую MTHourCell
константу, которая служит идентификатором повторного использования ячейки, и (3) сказать представлению коллекции использовать класс MTHourCell
для создания экземпляра новые клетки. В viewDidLoad
мы также устанавливаем цвет фона представления коллекции прозрачным.
1
|
#import «MTHourCell.h»
|
1
|
static NSString *HourCell = @»HourCell»;
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
— (void)viewDidLoad {
[super viewDidLoad];
// Load Location
self.location = [[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocation];
if (!self.location) {
[self.locationManager startUpdatingLocation];
}
// Configure Collection View
[self.collectionView setBackgroundColor:[UIColor clearColor]];
[self.collectionView registerClass:[MTHourCell class] forCellWithReuseIdentifier:HourCell];
}
|
3. Интерфейс пользователя справа
Шаг 1: Аутлеты
Несмотря на то, что в правильном представлении отображается много данных, реализация класса MTForecastViewController
не так уж сложна. Мы начнем с создания выхода для табличного представления в MTForecastViewController.h и приведем класс в соответствие с протоколами UITableViewDataSource
и UITableViewDelegate
.
1
2
3
4
5
6
7
|
#import <UIKit/UIKit.h>
@interface MTForecastViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end
|
Шаг 2: Создание пользовательского интерфейса
Создать пользовательский интерфейс так же просто, как добавить табличное представление к представлению контроллера представления, подключить выход, который мы создали минуту назад, и установить владельца файла в качестве dataSource
и delegate
dataSource
delegate
(рисунок 7).
Шаг 3: Заполнение табличного представления
Прежде чем заполнять табличное представление данными, нам нужно создать подкласс UITableViewCell
. Создайте новый класс Objective C, назовите его MTDayCell
и сделайте его подклассом UITableViewCell
(рисунок 8). Откройте MTDayCell.h и объявите пять выходов типа UILabel
. Как и в MTHourCell
классом MTHourCell
, реализация MTDayCell
не слишком сложна, как вы можете видеть ниже.
01
02
03
04
05
06
07
08
09
10
11
|
#import <UIKit/UIKit.h>
@interface MTDayCell : UITableViewCell
@property (strong, nonatomic) UILabel *labelDay;
@property (strong, nonatomic) UILabel *labelDate;
@property (strong, nonatomic) UILabel *labelTemp;
@property (strong, nonatomic) UILabel *labelWind;
@property (strong, nonatomic) UILabel *labelRain;
@end
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
#import «MTDayCell.h»
#define kMTCalendarWidth 44.0
#define kMTCalendarHeight 80.0
#define kMTCalendarMarginLeft 60.0
#define kMTLabelRightWidth 30.0
#define kMTLabelRightHeight 14.0
@interface MTDayCell ()
@property (strong, nonatomic) UIImageView *imageViewCalendar;
@end
@implementation MTDayCell
#pragma mark —
#pragma mark Initialization
— (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Helpers
CGSize size = self.contentView.frame.size;
// Configure Table View Cell
[self setSelectionStyle:UITableViewCellSelectionStyleNone];
// Initialize Image View Clock
self.imageViewCalendar = [[UIImageView alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 0.0, kMTCalendarWidth, kMTCalendarHeight)];
// Configure Image View Clock
[self.imageViewCalendar setContentMode:UIViewContentModeCenter];
[self.imageViewCalendar setImage:[UIImage imageNamed:@»background-calendar-day-cell»]];
[self.imageViewCalendar setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
[self.contentView addSubview:self.imageViewCalendar];
// Initialize Label Day
self.labelDay = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 10.0, kMTCalendarWidth, 20.0)];
// Configure Label Day
[self.labelDay setTextColor:[UIColor whiteColor]];
[self.labelDay setTextAlignment:NSTextAlignmentCenter];
[self.labelDay setBackgroundColor:[UIColor clearColor]];
[self.labelDay setFont:[UIFont fontWithName:@»GillSans» size:14.0]];
[self.labelDay setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
[self.contentView addSubview:self.labelDay];
// Initialize Label Date
self.labelDate = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 20.0, kMTCalendarWidth, 60.0)];
// Configure Label Date
[self.labelDate setTextColor:kMTColorGray];
[self.labelDate setTextAlignment:NSTextAlignmentCenter];
[self.labelDate setBackgroundColor:[UIColor clearColor]];
[self.labelDate setFont:[UIFont fontWithName:@»GillSans» size:24.0]];
[self.labelDate setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
[self.contentView addSubview:self.labelDate];
// Initialize Label Wind
self.labelWind = [[UILabel alloc] initWithFrame:CGRectMake(size.width — kMTLabelRightWidth, (size.height / 2.0) — kMTLabelRightHeight, kMTLabelRightWidth, kMTLabelRightHeight)];
// Configure Label Wind
[self.labelWind setTextColor:kMTColorGray];
[self.labelWind setTextAlignment:NSTextAlignmentCenter];
[self.labelWind setBackgroundColor:[UIColor clearColor]];
[self.labelWind setFont:[UIFont fontWithName:@»GillSans» size:12.0]];
[self.labelWind setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)];
[self.contentView addSubview:self.labelWind];
// Initialize Label Rain
self.labelRain = [[UILabel alloc] initWithFrame:CGRectMake(size.width — kMTLabelRightWidth, (size.height / 2.0), kMTLabelRightWidth, kMTLabelRightHeight)];
// Configure Label Rain
[self.labelRain setTextColor:kMTColorGray];
[self.labelRain setTextAlignment:NSTextAlignmentCenter];
[self.labelRain setBackgroundColor:[UIColor clearColor]];
[self.labelRain setFont:[UIFont fontWithName:@»GillSans» size:12.0]];
[self.labelRain setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
[self.contentView addSubview:self.labelRain];
// Initialize Label Temp
self.labelTemp = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarWidth + kMTCalendarMarginLeft + 12.0, 0.0, size.width — kMTCalendarWidth — kMTCalendarMarginLeft — kMTLabelRightWidth — 12.0, size.height)];
// Configure Label Temp
[self.labelTemp setTextColor:kMTColorGray];
[self.labelTemp setTextAlignment:NSTextAlignmentCenter];
[self.labelTemp setBackgroundColor:[UIColor clearColor]];
[self.labelTemp setFont:[UIFont fontWithName:@»GillSans-Bold» size:40.0]];
[self.labelTemp setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[self.contentView addSubview:self.labelTemp];
}
return self;
}
@end
|
Реализация протокола источника данных табличного представления очень похожа на реализацию протокола источника данных представления коллекции, который мы видели ранее. Мы также реализуем один метод протокола делегата табличного представления, tableView:heightForRowAtIndexPath:
чтобы установить высоту строки tableView:heightForRowAtIndexPath:
80.0
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.forecast ?
}
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.forecast count];
}
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MTDayCell *cell = [tableView dequeueReusableCellWithIdentifier:DayCell forIndexPath:indexPath];
// Fetch Data
NSDictionary *data = [self.forecast objectAtIndex:indexPath.row];
// Initialize Date Formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@»time»] doubleValue]];
// Configure Cell
[dateFormatter setDateFormat:@»EEE»];
[cell.labelDay setText:[dateFormatter stringFromDate:date]];
[dateFormatter setDateFormat:@»d»];
[cell.labelDate setText:[dateFormatter stringFromDate:date]];
float tempMin = [data[@»temperatureMin»] floatValue];
float tempMax = [data[@»temperatureMax»] floatValue];
[cell.labelTemp setText:[NSString stringWithFormat:@»%.0f°/%.0f°», tempMin, tempMax]];
[cell.labelWind setText:[NSString stringWithFormat:@»%.0f», [data[@»windSpeed»] floatValue]]];
float rainProbability = 0.0;
if (data[@»precipProbability»]) {
rainProbability = [data[@»precipProbability»] floatValue] * 100.0;
}
[cell.labelRain setText:[NSString stringWithFormat:@»%.0f», rainProbability]];
return cell;
}
|
1
2
3
|
— (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 80.0;
}
|
Чтобы все это работало, нам нужно (1) импортировать файл MTDayCell
класса MTDayCell
, (2) объявить статическую MTDayCell
константу для идентификатора повторного использования ячейки и (3) зарегистрировать MTDayCell
как класс для идентификатора повторного использования ячейки в viewDidLoad
. В updateView
мы перезагружаем табличное представление.
1
|
#import «MTDayCell.h»
|
1
|
static NSString *DayCell = @»DayCell»;
|
1
2
3
4
5
6
|
— (void)viewDidLoad {
[super viewDidLoad];
// Configure Table View
[self.tableView registerClass:[MTDayCell class] forCellReuseIdentifier:DayCell];
}
|
1
2
3
4
|
— (void)updateView {
// Reload Table View
[self.tableView reloadData];
}
|
4. Интерфейс пользователя справа
Шаг 1: Обновление пользовательского интерфейса
Понятно, что нам нужно внести некоторые существенные изменения в контроллер представления местоположений, чтобы реализовать дизайн Криса. Табличное представление контроллера представления местоположений будет включать два раздела вместо одного. В верхней части отобразятся сохраненные местоположения, а в нижней части зарезервировано значение температуры. Начните с открытия MTLocationsViewController.xib и удалите панель навигации, которую мы добавили в предыдущей статье (рисунок 9). Это означает, что мы также можем удалить выход для кнопки редактирования в MTLocationsViewController.h и действие editLocations
в MTLocationsViewController.m .
Шаг 2: Создание ячейки местоположения
Ячейки, которые отображают местоположения, имеют кнопку удаления слева. Чтобы это работало, нам нужно создать настраиваемую ячейку табличного представления. Создайте другой подкласс UITableViewCell
и назовите его MTLocationCell
(рисунок 10).Откройте MTLocationCell.h и создайте два свойства (1) buttonDelete
( UIButton
) и (2) labelLocation
( UILabel
). Как видите, реализация MTLocationCell
менее сложна, чем реализация MTHourCell
и MTDayCell
.
1
2
3
4
5
6
7
8
|
#import <UIKit/UIKit.h>
@interface MTLocationCell : UITableViewCell @property (strong, nonatomic ) UIButton *buttonDelete; @property (strong, nonatomic ) UILabel *labelLocation; @end
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#import "MTLocationCell.h" #define kMTButtonDeleteWidth 44.0 #define kMTLabelLocationMarginLeft 44.0 @implementation MTLocationCell #pragma mark —
#pragma mark Initialization - ( id )initWithStyle:(UITableViewCellStyle)style reuseIdentifier 🙁 NSString *)reuseIdentifier { self = [ super initWithStyle :style reuseIdentifier :reuseIdentifier]; if (self) {
// Helpers
CGSize size = self .contentView .frame .size ; // Initialize Delete Button self .buttonDelete = [ UIButton buttonWithType :UIButtonTypeCustom]; // Configure Delete Button [ self .buttonDelete setFrame :CGRectMake( 0 .0 , 0 .0 , kMTButtonDeleteWidth, size .height )]; [ self .buttonDelete setImage :[ UIImage imageNamed : @"button-delete-location-cell" ] forState :UIControlStateNormal]; [ self .buttonDelete setImage :[ UIImage imageNamed : @"button-delete-location-cell" ] forState :UIControlStateSelected]; [ self .buttonDelete setImage :[ UIImage imageNamed : @"button-delete-location-cell" ] forState :UIControlStateDisabled]; [ self .buttonDelete setImage :[ UIImage imageNamed : @"button-delete-location-cell" ] forState :UIControlStateHighlighted]; [ self .buttonDelete setAutoresizingMask 🙁 UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin)]; [ self .contentView addSubview : self .buttonDelete ]; // Initialize Location Label self .labelLocation = [[ UILabel alloc ] initWithFrame :CGRectMake(kMTLabelLocationMarginLeft, 0 .0 , size .width - kMTLabelLocationMarginLeft, size .height )]; // Configure Text Label [ self .labelLocation setTextColor :kMTColorGray]; [ self .labelLocation setBackgroundColor :[ UIColor clearColor ]]; [ self .labelLocation setFont :[ UIFont fontWithName : @"GillSans" size : 2 0 .0 ]]; [ self .labelLocation setAutoresizingMask 🙁 UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin)]; [ self .contentView addSubview : self .labelLocation ]; }
return self;
}
@end
|
Шаг 3. Обновление протокола источника данных табличного представления
Для реализации конструкции, нам необходимо обновить UITableViewDataSource
и UITableViewDelegate
протоколы. Начните с импорта заголовочного файла MTLocationCell
класса и категории, NSUserDefaults
которую мы создали ранее. Табличное представление будет содержать три типа ячеек, и нам нужно объявить идентификатор повторного использования для каждого типа (см. Ниже).
01
02
03
04
05
06
07
08
09
10
|
#import «MTLocationsViewController.h»
#import "MTLocationCell.h" #import "NSUserDefaults+Helpers.h" @interface MTLocationsViewController ()
@property (strong, nonatomic) NSMutableArray *locations;
@end
|
1
2
3
|
static NSString * AddLocationCell = @"AddLocationCell" ; static NSString *LocationCell = @»LocationCell»;
static NSString * SettingsCell = @"SettingsCell" ; |
В setupView
, мы настраиваем табличное представление путем (1) установки его separatorStyle
свойства UITableViewCellSeparatorStyleNone
и (2) регистрации класса для каждого идентификатора повторного использования.
1
2
3
4
5
6
7
8
9
|
— (void)setupView {
// Setup Table View [ self .tableView setSeparatorStyle :UITableViewCellSeparatorStyleNone]; // Register Class for Cell Reuse
[ self .tableView registerClass :[ UITableViewCell class ] forCellReuseIdentifier :AddLocationCell]; [ self .tableView registerClass :[ MTLocationCell class ] forCellReuseIdentifier :LocationCell]; [ self .tableView registerClass :[ UITableViewCell class ] forCellReuseIdentifier :SettingsCell]; }
|
В UITableViewDataSource
изменениях протокола значительно и реализация различных методов могут показаться сложными на первом. Однако большая часть сложности связана с вложенными if
утверждениями.
01
02
03
04
05
06
07
08
09
10
11
|
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2 ; }
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0 ) { return [ self .locations count ] + 1 ; }
return 2 ; }
|
Мы также реализуем tableView:titleForHeaderInSection:
и tableView:viewForHeaderInSection:
заменяем заголовки разделов по умолчанию на собственный дизайн, который соответствует дизайну приложения. При реализации tableView:viewForHeaderInSection:
важно также реализовать tableView:heightForHeaderInSection:
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
- ( NSString *)tableView:( UITableView *)tableView titleForHeaderInSection :(NSInteger)section { switch (section) { case 0 : { return NSLocalizedString( @"Locations" , nil ); break;
}
default: {
return NSLocalizedString( @"Temperature" , nil ); break;
}
}
return nil;
}
- ( UIView *)tableView:( UITableView *)tableView viewForHeaderInSection :(NSInteger)section { // Header Text NSString *text = [ self tableView :tableView titleForHeaderInSection :section]; // Helpers
CGRect labelFrame = CGRectMake( 1 2 .0 , 0 .0 , tableView .bounds .size .width , 4 4 .0 ); // Initialize Label UILabel *label = [[ UILabel alloc ] initWithFrame :labelFrame]; // Configure Label [label setText :text]; [label setTextColor :kMTColorOrange]; [label setFont :[ UIFont fontWithName : @"GillSans" size : 2 0 .0 ]]; [label setBackgroundColor :[ UIColor clearColor ]]; // Initialize View UIView *backgroundView = [[ UIView alloc ] initWithFrame :CGRectMake( 0 .0 , 0 .0 , tableView .bounds .size .width , 3 4 .0 )]; [backgroundView setBackgroundColor :[ UIColor clearColor ]]; [backgroundView addSubview :label]; return backgroundView; }
- (CGFloat)tableView:( UITableView *)tableView heightForHeaderInSection :(NSInteger)section { return 4 0 .0 ; }
|
Мы используем tableView:heightForFooterInSection:
для создания пробелов между верхней и нижней секциями. Это означает, что мы также должны реализовать tableView:viewForFooterInSection:
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
- ( UIView *)tableView:( UITableView *)tableView viewForFooterInSection :(NSInteger)section { // Initialize View UIView *backgroundView = [[ UIView alloc ] initWithFrame :CGRectMake( 0 .0 , 0 .0 , tableView .bounds .size .width , 3 4 .0 )]; [backgroundView setBackgroundColor :[ UIColor whiteColor ]]; return backgroundView; }
- (CGFloat)tableView:( UITableView *)tableView heightForFooterInSection :(NSInteger)section { if (section == 0 ) { return 4 0 .0 ; }
return 0 .0 ; }
|
Реализация tableView:cellForRowAtIndexPath:
немного сложнее из-за трех типов ячеек в табличном представлении.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil ; if (indexPath .section == 0 ) { if (indexPath.row == 0) {
cell = [tableView dequeueReusableCellWithIdentifier : AddLocationCell forIndexPath :indexPath]; } else {
cell = [tableView dequeueReusableCellWithIdentifier : LocationCell forIndexPath :indexPath]; }
} else {
cell = [tableView dequeueReusableCellWithIdentifier : SettingsCell forIndexPath :indexPath]; }
// Configure Cell
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
|
В configureCell:atIndexPath:
, мы настраиваем каждую ячейку. Как я писал ранее, сложность в основном связана с вложенными if
утверждениями. Нажатие кнопки удаления в ячейке местоположения отправляет сообщение deleteLocation:
контроллеру представления местоположений. Мы осуществим в deleteLocation:
ближайшее время.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
— (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// Helpers
UIFont *fontLight = [ UIFont fontWithName : @"GillSans-Light" size : 1 8 .0 ]; UIFont *fontRegular = [ UIFont fontWithName : @"GillSans" size : 1 8 .0 ]; // Background View Image UIImage *backgroundImage = [[ UIImage imageNamed : @"background-location-cell" ] resizableImageWithCapInsets :UIEdgeInsetsMake( 1 0 .0 , 0 .0 , 0 .0 , 1 0 .0 )]; // Configure Table View Cell
[cell .textLabel setFont :fontLight]; [cell .textLabel setTextColor :kMTColorGray]; [cell .textLabel setBackgroundColor :[ UIColor clearColor ]]; [cell setSelectionStyle :UITableViewCellSelectionStyleNone]; if (indexPath .section == 0 ) { if (indexPath.row == 0) {
[cell.textLabel setText:@»Add Current Location»];
[cell .imageView setContentMode :UIViewContentModeCenter]; [cell .imageView setImage :[ UIImage imageNamed : @"icon-add-location" ]]; // Background View Image backgroundImage = [[ UIImage imageNamed : @"background-add-location-cell" ] resizableImageWithCapInsets :UIEdgeInsetsMake( 1 0 .0 , 0 .0 , 0 .0 , 1 0 .0 )]; } else {
// Fetch Location
NSDictionary *location = [self.locations objectAtIndex:(indexPath.row — 1)];
// Configure Cell
[[( MTLocationCell *)cell buttonDelete ] addTarget : self action : @selector (deleteLocation:) forControlEvents :UIControlEventTouchUpInside]; [[( MTLocationCell *)cell labelLocation ] setText :[ NSString stringWithFormat : @"%@, %@" , location[MTLocationKeyCity], location[MTLocationKeyCountry]]]; }
} else {
if (indexPath.row == 0) {
[cell .textLabel setText :NSLocalizedString( @"Fahrenheit" , nil )]; if ([ NSUserDefaults isDefaultCelcius ]) { [cell .textLabel setFont :fontLight]; [cell .textLabel setTextColor :kMTColorGray]; } else {
[cell .textLabel setFont :fontRegular]; [cell .textLabel setTextColor :kMTColorGreen]; }
} else {
[cell .textLabel setText :NSLocalizedString( @"Celsius" , nil )]; if ([ NSUserDefaults isDefaultCelcius ]) { [cell .textLabel setFont :fontRegular]; [cell .textLabel setTextColor :kMTColorGreen]; } else {
[cell .textLabel setFont :fontLight]; [cell .textLabel setTextColor :kMTColorGray]; }
}
}
if (backgroundImage) { // Background View UIImageView *backgroundView = [[ UIImageView alloc ] initWithFrame :CGRectMake( 0 .0 , 0 .0 , cell .frame .size .width , cell .frame .size .height )]; [backgroundView setAutoresizingMask 🙁 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; [backgroundView setImage :backgroundImage]; [cell setBackgroundView :backgroundView]; }
}
|
Поскольку местоположения теперь можно удалять, нажимая кнопку удаления в ячейках местоположений, сами строки больше не нужно редактировать. Это означает, что реализация tableView:canEditRowAtIndexPath:
может быть сведена к возврату, NO
а реализация tableView:commitEditingStyle:forRowAtIndexPath:
может быть полностью удалена.
1
2
3
|
— (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
|
Шаг 4: Обновление протокола делегата табличного представления
В tableView:didSelectRowAtIndexPath:
добавим немного больше сложности из-за включения второго раздела, содержащего настройку температуры. Благодаря нашей категории NSUserDefaults
, реализация проста и лаконична.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
— (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath .section == 0 ) { if (indexPath.row == 0) {
// Notify Delegate
[self.delegate controllerShouldAddCurrentLocation:self];
} else {
// Fetch Location
NSDictionary *location = [self.locations objectAtIndex:(indexPath.row — 1)];
// Notify Delegate
[self.delegate controller:self didSelectLocation:location];
}
} else {
if (indexPath .row == 0 && [ NSUserDefaults isDefaultCelcius ]) { [ NSUserDefaults setDefaultToFahrenheit ]; } else if (![ NSUserDefaults isDefaultCelcius ]) { [ NSUserDefaults setDefaultToCelcius ]; }
// Update Section [ self .tableView reloadSections :[ NSIndexSet indexSetWithIndex : 1 ] withRowAnimation :UITableViewRowAnimationNone]; }
// Show Center View Controller
[self.viewDeckController closeLeftViewAnimated:YES];
}
|
Строки в дизайне Криса немного выше, чем высота по умолчанию в 44 пункта. Чтобы применить эту деталь на практике, мы реализуем, tableView:heightForRowAtIndexPath:
как показано ниже.
1
2
3
|
- (CGFloat)tableView:( UITableView *)tableView heightForRowAtIndexPath 🙁 NSIndexPath *)indexPath { return 5 0 .0 ; }
|
Шаг 5: Удаление местоположений (повторно)
Последнее, что нам нужно сделать, — это реализовать deleteLocation:
метод, который вызывается при нажатии кнопки удаления в ячейке местоположения. Реализация более многословна, чем вы могли бы ожидать. Вывести номер строки из ячейки, которой принадлежит кнопка удаления, не так тривиально, как должно быть. Однако, когда у нас есть индексный путь к ячейке, которой принадлежит кнопка, нам нужно только обновить массив местоположений, обновить базу данных по умолчанию для пользователя и обновить табличное представление.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
- ( void )deleteLocation:( id )sender { UITableViewCell *cell = ( UITableViewCell *)[[( UIButton *)sender superview ] superview ]; NSIndexPath *indexPath = [ self .tableView indexPathForCell :cell]; // Update Locations [ self .locations removeObjectAtIndex :(indexPath .row - 1 )]; // Update User Defaults
[[ NSUserDefaults standardUserDefaults ] setObject : self .locations forKey :MTRainUserDefaultsLocations]; // Update Table View
[ self .tableView deleteRowsAtIndexPaths :@[indexPath] withRowAnimation :UITableViewRowAnimationTop]; }
|
5. Последний штрих
Чтобы завершить наш проект, нам нужно заменить образы запуска по умолчанию на изображения, предоставленные Крисом Кэри. Образы запуска также включены в исходные файлы этой статьи.
Создайте и запустите приложение, чтобы увидеть конечный результат в действии. Несмотря на то, что мы потратили довольно много времени на создание и разработку приложения, все еще есть некоторые неровности. Также было бы неплохо реализовать механизм кэширования, чтобы мы могли показывать кэшированные данные пользователю до тех пор, пока не будет возвращен запрос к API Forecast. Вот несколько примеров доработок, которые мы можем добавить в наше приложение.
Вывод
Создание пользовательского интерфейса было довольно трудоемким, но код не был таким уж сложным. Я надеюсь, что этот урок показал вам, что отличный дизайн может сделать для приложения. Потребительские приложения, в частности, действительно выигрывают от привлекательного, свежего дизайна, подобного тому, который мы использовали в этом руководстве.