Статьи

Создать приложение погоды с прогнозом — пользовательский интерфейс

В последней статье этой серии я покажу вам, как создать пользовательский интерфейс нашего погодного приложения. Благодаря работе Криса Кэри , постоянного участника Vectortuts + , у нас есть прекрасный дизайн, с которым мы можем работать!


Для разработки нашего погодного приложения я сотрудничал с Крисом Кэри , постоянным участником Vectortuts + . Крис создал красивый дизайн, который мы можем использовать для создания пользовательского интерфейса приложения. После того как Крис передал мне дизайн в качестве векторной иллюстрации, я нарезал иллюстрацию и подготовил ее для использования в нашем проекте. Вы можете найти нарезанные изображения в исходных файлах этой статьи.

Пользовательский интерфейс - Дизайн Криса Кэри
Рисунок 1: Дизайн Криса Кэри

Из-за ограниченной возможности настройки UIKit нам придется пойти на несколько компромиссов при реализации дизайна Криса. Однако конечный результат будет очень похож на дизайн, который Крис создал для нас. Например, UISwitch класса UISwitch UISwitch очень ограничена, и поэтому мы будем использовать другой подход для реализации настройки температуры. Крис использовал два пользовательских шрифта для своего дизайна, Maven Pro и Mission Gothic. Хотя iOS поддерживает пользовательские шрифты, мы будем использовать только те шрифты, которые доступны на iOS.


Всякий раз, когда контроллер представления погоды получает данные о погоде от 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»;

После получения ответа от 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];
}

Шаги практически идентичны в классе 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 {
 
}

Несмотря на то, что центральное представление содержит много информации, это не так сложно реализовать. Давайте начнем с создания ряда торговых точек и одного нового действия. Обновите 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];
}

Откройте MTWeatherViewController.xib и создайте пользовательский интерфейс, как показано на рисунке 2. При создании пользовательского интерфейса важно убедиться, что пользовательский интерфейс отображается правильно как на 3,5- дюймовом экране, так и на 4- дюймовом экране iPhone 5. Вы можете проверить это, выбрав представление контроллера представления и изменив атрибут Size в Инспекторе Атрибутов . Чтобы достичь желаемого результата, вам нужно настроить ограничения на автоматическое расположение элементов пользовательского интерфейса. Цель состоит в том, чтобы данные о погоде прилипали к верхней части представления, а сводный вид приклеивается к нижней части. Значки рядом с метками времени, ветра и дождя являются экземплярами UIImageView .

Создание приложений с прогнозом погоды - Пользовательский интерфейс - Создание пользовательских интерфейсов
Рисунок 2: Создание пользовательского интерфейса контроллера Weather View

Сконфигурируйте метки и кнопки, как показано на рисунке 2. Это включает в себя правильное выравнивание текста меток, установку типов обеих кнопок на Custom , назначение Владельцу файла dataSource delegate dataSource и delegate , а также установку направления прокрутки представления собрания. макет потока в горизонтальном положении. Я фанат Gill Sans, поэтому этот шрифт я выбрал для этого проекта. Прежде чем вернуться к файлу реализации контроллера представления погоды, подключите розетки и действие, которое мы создали ранее. В дополнение к меткам и кнопкам я также добавил представление изображения в представление контроллера представления для отображения фонового изображения.

Как я уже упоминал во введении, вы можете найти обложку приложения в исходных файлах этого руководства. Создайте папку с именем Artwork в вашем проекте Xcode и перетащите обложку в эту папку.

В настоящее время мы регистрируем ответ 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 и преобразованиями температуры.

Прежде чем мы создадим класс, который обрабатывает преобразование температуры, мы должны иметь возможность сохранить текущую настройку температуры в базе данных пользователя по умолчанию. Повторно посетите MTConstants.h / .m и объявите строковую константу с именем MTRainUserDefaultsTemperatureUnit .

1
extern NSString * const MTRainUserDefaultsTemperatureUnit;
1
NSString * const MTRainUserDefaultsTemperatureUnit = @»temperatureUnit»;

Чтобы упростить работу с настройками, я часто создаю категорию в NSUserDefaults которая позволяет мне быстро и элегантно получать доступ к настройкам приложения. Позвольте мне показать вам, что я имею в виду. Создайте новую категорию Objective-C (рисунок 3), назовите категорию Helpers и сделайте ее категорией в NSUserDefaults (рисунок 4). В NSUserDefaults + Helpers.h мы объявляем три метода класса, как показано ниже.

Создание приложений с прогнозом погоды - Пользовательский интерфейс - Создание новой категории Objective-C
Рисунок 3: Создание новой категории Objective C
Создание приложений с прогнозом погоды - Пользовательский интерфейс - Создание категорий в NSUserDefaults
Рисунок 4: Создание категории в NSUserDefaults
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 , преобразует его в число с плавающей точкой и возвращает отформатированную строку, основанную на настройке температуры.

Пользовательский интерфейс - Создание класса MTSettings
Рисунок 5: Создание класса MTSettings
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]];
}

Чтобы заполнить представление коллекции, нам сначала нужно создать подкласс UICollectionViewCell . Создайте новый класс Objective C, назовите его MTHourCell и сделайте его подклассом UICollectionViewCell (рисунок 6). Создание пользовательских ячеек таблицы или коллекции может быть трудоемким и кропотливым. Если вы хотите больше узнать о создании пользовательских ячеек таблиц и представлений коллекций, то я предлагаю вам взглянуть на учебник, который я написал несколько недель назад.

Создание приложения «Погода» с прогнозом - Пользовательский интерфейс - Создание пользовательского представления коллекции
Рисунок 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];
}

Несмотря на то, что в правильном представлении отображается много данных, реализация класса 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

Создать пользовательский интерфейс так же просто, как добавить табличное представление к представлению контроллера представления, подключить выход, который мы создали минуту назад, и установить владельца файла в качестве dataSource и delegate dataSource delegate (рисунок 7).

Создание пользовательских прогнозов погоды - Пользовательский интерфейс - Создание пользовательских интерфейсов
Рисунок 7: Создание пользовательского интерфейса контроллера Forecast View

Прежде чем заполнять табличное представление данными, нам нужно создать подкласс UITableViewCell . Создайте новый класс Objective C, назовите его MTDayCell и сделайте его подклассом UITableViewCell (рисунок 8). Откройте MTDayCell.h и объявите пять выходов типа UILabel . Как и в MTHourCell классом MTHourCell , реализация MTDayCell не слишком сложна, как вы можете видеть ниже.

Создание приложений с прогнозом погоды - Пользовательский интерфейс - Создание пользовательских блоков табличного представления
Рисунок 8: Создание пользовательской ячейки табличного представления
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];
}

Понятно, что нам нужно внести некоторые существенные изменения в контроллер представления местоположений, чтобы реализовать дизайн Криса. Табличное представление контроллера представления местоположений будет включать два раздела вместо одного. В верхней части отобразятся сохраненные местоположения, а в нижней части зарезервировано значение температуры. Начните с открытия MTLocationsViewController.xib и удалите панель навигации, которую мы добавили в предыдущей статье (рисунок 9). Это означает, что мы также можем удалить выход для кнопки редактирования в MTLocationsViewController.h и действие editLocations в MTLocationsViewController.m .

Пользовательский интерфейс - Обновление пользовательского интерфейса.
Рисунок 9: Обновление пользовательского интерфейса контроллера Locations View

Ячейки, которые отображают местоположения, имеют кнопку удаления слева. Чтобы это работало, нам нужно создать настраиваемую ячейку табличного представления. Создайте другой подкласс UITableViewCell и назовите его MTLocationCell (рисунок 10).Откройте MTLocationCell.h и создайте два свойства (1) buttonDelete( UIButton) и (2) labelLocation( UILabel). Как видите, реализация MTLocationCellменее сложна, чем реализация MTHourCellи MTDayCell.

Создание приложения погоды с прогнозом - Пользовательский интерфейс - Создание пользовательской ячейки табличного представления
Рисунок 10: Создание пользовательской ячейки табличного представления
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:20.0]];
        [self.labelLocation setAutoresizingMask🙁UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin)];
        [self.contentView addSubview:self.labelLocation];
    }
 
    return self;
}
 
@end

Для реализации конструкции, нам необходимо обновить 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(12.0, 0.0, tableView.bounds.size.width, 44.0);
 
    // Initialize Label
    UILabel *label = [[UILabel alloc] initWithFrame:labelFrame];
 
    // Configure Label
    [label setText:text];
    [label setTextColor:kMTColorOrange];
    [label setFont:[UIFont fontWithName:@"GillSans" size:20.0]];
    [label setBackgroundColor:[UIColor clearColor]];
 
    // Initialize View
    UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, tableView.bounds.size.width, 34.0)];
    [backgroundView setBackgroundColor:[UIColor clearColor]];
    [backgroundView addSubview:label];
 
    return backgroundView;
}
 
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 40.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, 34.0)];
    [backgroundView setBackgroundColor:[UIColor whiteColor]];
 
    return backgroundView;
}
 
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    if (section == 0) {
        return 40.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:18.0];
    UIFont *fontRegular = [UIFont fontWithName:@"GillSans" size:18.0];
 
    // Background View Image
    UIImage *backgroundImage = [[UIImage imageNamed:@"background-location-cell"] resizableImageWithCapInsets:UIEdgeInsetsMake(10.0, 0.0, 0.0, 10.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(10.0, 0.0, 0.0, 10.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;
}

В 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 50.0;
}

Последнее, что нам нужно сделать, — это реализовать 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];
}

Чтобы завершить наш проект, нам нужно заменить образы запуска по умолчанию на изображения, предоставленные Крисом Кэри. Образы запуска также включены в исходные файлы этой статьи.

Создайте и запустите приложение, чтобы увидеть конечный результат в действии. Несмотря на то, что мы потратили довольно много времени на создание и разработку приложения, все еще есть некоторые неровности. Также было бы неплохо реализовать механизм кэширования, чтобы мы могли показывать кэшированные данные пользователю до тех пор, пока не будет возвращен запрос к API Forecast. Вот несколько примеров доработок, которые мы можем добавить в наше приложение.

Создание пользовательского интерфейса было довольно трудоемким, но код не был таким уж сложным. Я надеюсь, что этот урок показал вам, что отличный дизайн может сделать для приложения. Потребительские приложения, в частности, действительно выигрывают от привлекательного, свежего дизайна, подобного тому, который мы использовали в этом руководстве.