В первой статье этой серии мы заложили основу проекта, настроив проект и создав структуру приложения. В этой статье мы используем библиотеку AFNetworking для взаимодействия с Forecast API.
Вступление
В первом выпуске этой серии мы заложили основу нашего погодного приложения. Пользователи могут добавлять свое текущее местоположение и переключаться между местоположениями. В этом руководстве мы будем использовать библиотеку AFNetworking, чтобы запросить у API прогноза данные о погоде выбранного в данный момент местоположения.
Если вы хотите следовать, вам понадобится ключ API прогноза. Вы можете получить ключ API, зарегистрировавшись разработчиком в Forecast . Регистрация бесплатна, поэтому я рекомендую вам воспользоваться услугой Прогноз погоды. Вы можете найти свой ключ API в нижней части панели инструментов (рисунок 1).
1. Подклассы AFHTTPClient
Как я писал ранее в этой статье, мы будем использовать библиотеку AFNetworking для связи с Forecast API. При работе с AFNetworking существует несколько вариантов, но для обеспечения будущего приложения мы AFHTTPClient
класс AFHTTPClient
. Этот класс предназначен для использования веб-служб, таких как Forecast API. Несмотря на то, что мы будем иметь доступ только к одной конечной точке API, все же полезно использовать AFHTTPClient
как вы узнаете через несколько минут.
Рекомендуется создать подкласс AFHTTPClient
для каждого веб-сервиса. Поскольку мы уже добавили AFNetworking в наш проект в предыдущем уроке, мы можем сразу же начать создавать подклассы AFHTTPClient
.
Шаг 1. Создайте класс
Создайте новый класс Objective C, назовите его MTForecastClient
и сделайте его подклассом AFHTTPClient
(рисунок 2).
Шаг 2: Создание объекта Singleton
Мы примем шаблон синглтона, чтобы было проще использовать класс MTForecastClient
в нашем проекте. Это означает, что только один экземпляр класса является живым в любой момент времени для жизненного цикла приложения. Скорее всего, вы уже знакомы с одноэлементным шаблоном, поскольку он является общим шаблоном во многих объектно-ориентированных языках программирования. На первый взгляд, шаблон синглтона кажется очень удобным, но есть ряд предостережений, на которые стоит обратить внимание. Вы можете узнать больше о синглетонах, прочитав эту прекрасную статью Мэтта Галлахера.
Создание одноэлементного объекта довольно просто в Objective-C. Начните с объявления метода класса в MTForecastClient.h, чтобы обеспечить открытый доступ к одноэлементному объекту (см. Ниже).
1
2
3
4
5
6
7
8
9
|
#import «AFHTTPClient.h»
@interface MTForecastClient : AFHTTPClient
#pragma mark —
#pragma mark Shared Client
+ (MTForecastClient *)sharedClient;
@end
|
На sharedClient
взгляд реализация sharedClient
может показаться сложной, но это не так сложно, когда вы понимаете, что происходит. Сначала мы объявляем две статические переменные: (1) predicate
типа dispatch_once_t
и (2) _sharedClient
типа MTForecastClient
. Как следует из названия, predicate
— это предикат, который мы используем в сочетании с функцией dispatch_once
. При работе с переменной типа dispatch_once_t
важно, чтобы она объявлялась статически. Вторая переменная _sharedClient
будет хранить ссылку на одноэлементный объект.
Функция dispatch_once
получает указатель на структуру dispatch_once_t
, предикат и блок. Прелесть dispatch_once
заключается в том, что он будет выполнять блок один раз за время существования приложения, а это именно то, что нам нужно. Функция dispatch_once
не имеет много применений, но это определенно одно из них. В блоке, который мы передаем dispatch_once
, мы создаем объект singleton и сохраняем ссылку в _sharedClient
. Безопаснее вызывать alloc
и init
отдельно, чтобы избежать состояния гонки, которое потенциально может привести к тупику. Э-э … что? Вы можете прочитать больше о мельчайших подробностях в Stack Overflow .
01
02
03
04
05
06
07
08
09
10
11
|
+ (MTForecastClient *)sharedClient {
static dispatch_once_t predicate;
static MTForecastClient *_sharedClient = nil;
dispatch_once(&predicate, ^{
_sharedClient = [self alloc];
_sharedClient = [_sharedClient initWithBaseURL:[self baseURL]];
});
return _sharedClient;
}
|
Важная вещь, которую нужно понять о реализации sharedClient
класса sharedClient
заключается в том, что инициализатор initWithBaseURL:
вызывается только один раз. Объект singleton хранится в статической переменной _sharedClient
, которая возвращается методом класса sharedClient
.
Шаг 3: Настройка клиента
В sharedClient
мы вызываем initWithBaseURL:
который, в свою очередь, вызывает baseURL
, еще один метод класса. В initWithBaseURL:
мы устанавливаем заголовок по умолчанию, что означает, что клиент добавляет этот заголовок к каждому отправляемому запросу. Это одно из преимуществ работы с классом AFHTTPClient
. В initWithBaseURL:
мы также регистрируем класс операций HTTP, вызывая registerHTTPOperationClass:
Библиотека AFNetworking предоставляет ряд специализированных классов операций. Одним из этих классов является класс AFJSONRequestOperation
, который делает взаимодействие с JSON API очень простым. Поскольку Forecast API возвращает ответ JSON, класс AFJSONRequestOperation
является хорошим выбором. registerClass:forCellReuseIdentifier:
registerHTTPOperationClass:
метод работает аналогично тому, как работает registerClass:forCellReuseIdentifier:
класса UITableView
. Сообщая клиенту, какой класс операций мы хотим использовать для взаимодействия с веб-сервисом, он будет создавать экземпляры этого класса для нас под капотом. Почему это полезно, станет ясно через несколько минут.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
— (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
// Accept HTTP Header
[self setDefaultHeader:@»Accept» value:@»application/json»];
// Register HTTP Operation Class
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
}
return self;
}
|
Реализация baseURL
— не более чем удобный метод для создания базового URL клиента. Базовый URL-адрес — это URL-адрес, который клиент использует для доступа к веб-службе. Это URL без каких-либо имен методов или параметров. Базовый URL для API прогноза: https://api.forecast.io/forecast/ /
https://api.forecast.io/forecast/ /
1
2
3
|
+ (NSURL *)baseURL {
return [NSURL URLWithString:[NSString stringWithFormat:@»https://api.forecast.io/forecast/%@/», MTForecastAPIKey]];
}
|
Возможно, вы заметили, что в реализации baseURL
я использовал другую строковую константу для хранения ключа API. Это может показаться ненужным, поскольку мы используем ключ API только в одном месте. Однако рекомендуется хранить данные приложения в одном месте или в списке свойств.
1
2
3
|
#pragma mark —
#pragma mark Forecast API
extern NSString * const MTForecastAPIKey;
|
1
2
3
|
#pragma mark —
#pragma mark Forecast API
NSString * const MTForecastAPIKey = @»xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx»;
|
Шаг 4: Добавление вспомогательного метода
Прежде чем мы продолжим, я хотел бы расширить класс MTForecastClient
, добавив вспомогательный или вспомогательный метод, который облегчит выполнение запросов к Forecast API. Этот удобный метод примет местоположение и блок завершения. Блок завершения выполняется после завершения запроса. Чтобы упростить работу с блоками, рекомендуется объявить пользовательский тип блока, как показано ниже. Если вы все еще чувствуете себя некомфортно, пользуясь блоками, я рекомендую прочитать эту замечательную статью Акиэля Кхана .
Блок принимает два аргумента: (1) логическое значение, указывающее, был ли запрос успешным, и (2) словарь с ответом на запрос. requestWeatherForCoordinate:completion:
метод requestWeatherForCoordinate:completion:
принимает координаты местоположения ( CLLocationCoordinate2D
) и блока завершения. Используя блок завершения, мы можем избежать создания пользовательского протокола делегата или использовать уведомления. Блоки идеально подходят для этого типа сценария.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
#import «AFHTTPClient.h»
typedef void (^MTForecastClientCompletionBlock)(BOOL success, NSDictionary *response);
@interface MTForecastClient : AFHTTPClient
#pragma mark —
#pragma mark Shared Client
+ (MTForecastClient *)sharedClient;
#pragma mark —
#pragma mark Instance Methods
— (void)requestWeatherForCoordinate:(CLLocationCoordinate2D)coordinate completion:(MTForecastClientCompletionBlock)completion;
@end
|
In
requestWeatherForCoordinate:completion:
, we invoke getPath:success:failure:
, a method declared in AFHTTPClient
. The first argument is the path that is appended to the base URL that we created earlier. The second and third arguments are blocks that are executed when the request succeeds and fails, respectively. The success and failure blocks are pretty simple. If a completion block was passed to requestWeatherForCoordinate:completion:
, we execute the block and pass a boolean value and the response dictionary (or nil
in the failure block). In the failure block, we log the error from the failure block to the console to facilitate debugging.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
— (void)requestWeatherForCoordinate:(CLLocationCoordinate2D)coordinate completion:(MTForecastClientCompletionBlock)completion {
NSString *path = [NSString stringWithFormat:@»%f,%f», coordinate.latitude, coordinate.longitude];
[self getPath:path parameters:nil success:^(AFHTTPRequestOperation *operation, id response) {
if (completion) {
completion(YES, response);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) {
completion(NO, nil);
NSLog(@»Unable to fetch weather data due to error %@ with user info %@.», error, error.userInfo);
}
}];
}
|
Вы можете задаться вопросом, что такое объект response
в блоках успеха или ссылки. Несмотря на то, что Forecast API возвращает ответ JSON, объект response
в блоке успеха является экземпляром NSDictionary
. Преимущество работы с классом AFJSONHTTPRequestOperation
, который мы зарегистрировали в initWithBaseURL:
заключается в том, что он принимает ответ JSON и автоматически создает объект из данных ответа, словарь в этом примере.
2. Запрос API прогноза
Шаг 1: Изменить setLocation:
Вооруженный классом MTForecastClient
, пришло время запросить API прогноза и получить данные о погоде для выбранного в данный момент местоположения. Наиболее подходящее место для этого — метод MTWeatherViewController
класса MTWeatherViewController
. Изменить setLocation:
метод, как показано ниже. Как видите, все, что мы делаем, это вызываем fetchWeatherData
, еще один вспомогательный метод.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
— (void)setLocation:(NSDictionary *)location {
if (_location != location) {
_location = location;
// Update User Defaults
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setObject:location forKey:MTRainUserDefaultsLocation];
[ud synchronize];
// Post Notification
NSNotification *notification1 = [NSNotification notificationWithName:MTRainLocationDidChangeNotification object:self userInfo:location];
[[NSNotificationCenter defaultCenter] postNotification:notification1];
// Update View
[self updateView];
// Request Location
[self fetchWeatherData];
}
}
|
Вы когда-нибудь задумывались, почему я использую так много вспомогательных методов в своем коде? Причина проста. Оборачивая функциональность в вспомогательные методы, очень легко повторно использовать код в разных местах проекта. Однако главное преимущество заключается в том, что он помогает бороться с дублированием кода. Дублирование кода — это то, что вы всегда должны стараться избегать, насколько это возможно. Еще одним преимуществом использования вспомогательных методов является то, что он делает ваш код более читабельным. Создав методы, которые делают одно, и предоставив правильно выбранное имя метода, легче быстро читать и обрабатывать ваш код.
Шаг 2: Отправка запроса
Настало время использовать библиотеку SVProgressHUD
. Мне очень нравится эта библиотека, потому что она очень проста в использовании, не загромождая кодовую базу проекта. Посмотрите на реализацию fetchWeatherData
ниже. Мы начнем с отображения прогресса HUD, а затем передадим структуру ( CLLocationCoordinate2D
) в удобный метод, который мы создали ранее, requestWeatherForCoordinate:completion:
В блоке завершения мы скрываем прогресс HUD и записываем ответ в консоль.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
— (void)fetchWeatherData {
// 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];
NSLog(@»Response > %@», response);
}];
}
|
Прежде чем создавать и запускать приложение, импортируйте файл MTForecastClient
класса MTForecastClient в MTWeatherViewController.m .
01
02
03
04
05
06
07
08
09
10
11
12
13
|
#import «MTWeatherViewController.h»
#import «MTForecastClient.h»
@interface MTWeatherViewController () <CLLocationManagerDelegate> {
BOOL _locationFound;
}
@property (strong, nonatomic) NSDictionary *location;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end
|
Что происходит, когда устройство не подключено к сети? Вы думали об этом сценарии? С точки зрения пользовательского опыта рекомендуется уведомлять пользователя, когда приложение не может запросить данные из API Forecast. Позвольте мне показать, как это сделать с помощью библиотеки AFNetworking.
3. Достижимость
Есть ряд библиотек, которые предоставляют эту функциональность, но мы будем придерживаться AFNetworking. Apple также предоставляет пример кода , но он немного устарел и не поддерживает ARC.
AFNetworking действительно охватывает блоки, что, безусловно, является одной из причин, по которой эта библиотека стала настолько популярной. Отслеживать изменения достижимости так же просто, как передать блок в setReachabilityStatusChangeBlock:
другой метод класса AFHTTPClient
. Блок выполняется каждый раз, когда изменяется состояние достижимости, и он принимает один аргумент типа AFNetworkReachabilityStatus
. Взгляните на обновленный initWithBaseURL:
метод класса MTForecastClient
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
— (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
// Accept HTTP Header
[self setDefaultHeader:@»Accept» value:@»application/json»];
// Register HTTP Operation Class
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Reachability
__weak typeof(self)weakSelf = self;
[self setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
[[NSNotificationCenter defaultCenter] postNotificationName:MTRainReachabilityStatusDidChangeNotification object:weakSelf];
}];
}
return self;
}
|
Чтобы избежать цикла сохранения, мы передаем слабую ссылку на одноэлементный объект в блоке, который мы передаем, setReachabilityStatusChangeBlock:
Даже если вы используете ARC в своих проектах, вам все равно нужно знать о таких тонких проблемах памяти, как эта. Имя уведомления, которое мы публикуем, является другой строковой константой, объявленной в MTConstants.h / .m .
1
|
extern NSString * const MTRainReachabilityStatusDidChangeNotification;
|
1
|
NSString * const MTRainReachabilityStatusDidChangeNotification = @»com.mobileTuts.MTRainReachabilityStatusDidChangeNotification»;
|
Причина публикации уведомления в блоке изменения состояния достижимости состоит в том, чтобы другим частям приложения было проще обновляться при изменении достижимости устройства. Чтобы убедиться, что класс MTWeatherViewController
уведомлен об изменениях достижимости, экземпляры класса добавляются в качестве наблюдателя для уведомлений, отправляемых клиентом Forecast, как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
— (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(reachabilityStatusDidChange:) name:MTRainReachabilityStatusDidChangeNotification object:nil];
}
return self;
}
|
Это также означает, что нам нужно удалить экземпляр в качестве наблюдателя в методе dealloc
. Это деталь, которую часто упускают из виду.
1
2
3
4
|
— (void)dealloc {
// Remove Observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
|
Реализация reachabilityStatusDidChange:
настоящее время довольно reachabilityStatusDidChange:
. Мы обновим его реализацию, как только создадим пользовательский интерфейс приложения.
1
2
3
4
|
— (void)reachabilityStatusDidChange:(NSNotification *)notification {
MTForecastClient *forecastClient = [notification object];
NSLog(@»Reachability Status > %i», forecastClient.networkReachabilityStatus);
}
|
4. Обновление данных
Прежде чем закончить этот пост, я хочу добавить две дополнительные функции: (1) получение данных о погоде, когда приложение становится активным, и (2) добавление возможности вручную обновлять данные о погоде. Мы могли бы реализовать таймер, который выбирает свежие данные каждый час или около того, но, по моему мнению, это не обязательно для погодных приложений. Большинство пользователей запускают приложение, смотрят на погоду и помещают приложение в фоновом режиме. Поэтому необходимо только получать свежие данные, когда пользователь запускает приложение. Это означает, что нам нужно прослушивать уведомления MTWeatherViewController
классе MTWeatherViewController
. Как и для мониторинга изменений достижимости, мы добавляем экземпляры класса в качестве наблюдателей уведомлений типа UIApplicationDidBecomeActiveNotification
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
— (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];
}
return self;
}
|
В applicationDidBecomeActive:
мы проверяем, что location
установлено (не nil
), потому что это не всегда будет верно. Если местоположение действительно, мы получаем данные о погоде.
1
2
3
4
5
|
— (void)applicationDidBecomeActive:(NSNotification *)notification {
if (self.location) {
[self fetchWeatherData];
}
}
|
Я также изменил fetchWeatherData
чтобы запрашивать API прогноза только в том случае, если устройство подключено к сети.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
— (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];
// NSLog(@»Response > %@», response);
}];
}
|
Давайте добавим кнопку в контроллер просмотра погоды, к которой пользователь может нажать, чтобы вручную обновить данные о погоде. Создайте выход в MTWeatherViewController.h и создайте действие refresh:
в MTWeatherViewController.m .
01
02
03
04
05
06
07
08
09
10
|
#import <UIKit/UIKit.h>
#import «MTLocationsViewController.h»
@interface MTWeatherViewController : UIViewController <MTLocationsViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UILabel *labelLocation;
@property (weak, nonatomic) IBOutlet UIButton *buttonRefresh;
@end
|
1
2
3
4
5
|
— (IBAction)refresh:(id)sender {
if (self.location) {
[self fetchWeatherData];
}
}
|
Откройте MTWeatherViewController.xib , добавьте кнопку в представление контроллера представления с заголовком « Обновить» и подключите розетку и действие с помощью кнопки (рисунок 3). Причиной создания розетки для кнопки является возможность ее отключения, когда нет доступного сетевого подключения. Чтобы это работало, нам нужно обновить метод reachabilityStatusDidChange:
как показано ниже.
1
2
3
4
5
6
7
|
— (void)reachabilityStatusDidChange:(NSNotification *)notification {
MTForecastClient *forecastClient = [notification object];
NSLog(@»Reachability Status > %i», forecastClient.networkReachabilityStatus);
// Update Refresh Button
self.buttonRefresh.enabled = (forecastClient.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable);
}
|
Нет необходимости временно отключать кнопку обновления, когда запрос обрабатывается в fetchWeatherData
потому что HUD прогресса добавляет слой поверх представления контроллера представления, который не позволяет пользователю нажимать кнопку более одного раза. Создайте и запустите приложение, чтобы проверить все.
Бонус: удаление локаций
Читатель спросил меня, как удалить местоположения из списка, поэтому я включил его сюда для полноты картины. Первое, что нам нужно сделать, это сообщить табличному представлению, какие строки можно редактировать, реализовав tableView:canEditRowAtIndexPath:
протокола UITableViewDataSource
. Этот метод возвращает YES
если строка в indexPath
является редактируемой, и NO
если это не так. Реализация проста, как вы можете видеть ниже. Каждая строка является редактируемой, за исключением первой строки и текущего выбранного местоположения.
01
02
03
04
05
06
07
08
09
10
|
— (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
return NO;
}
// Fetch Location
NSDictionary *location = [self.locations objectAtIndex:(indexPath.row — 1)];
return ![self isCurrentLocation:location];
}
|
Чтобы проверить, является ли location
текущим местоположением, мы используем другой вспомогательный метод isCurrentLocation:
в котором мы выбираем текущее местоположение и сравниваем координаты местоположений. Было бы лучше (и проще), если бы мы присвоили уникальный идентификатор каждому местоположению, хранящемуся в базе данных по умолчанию пользователя. Это не только облегчит сравнение местоположений, но и позволит нам сохранить уникальный идентификатор текущего местоположения в базе данных по умолчанию пользователя и найти его в массиве местоположений. Проблема с текущей реализацией состоит в том, что местоположения с точно такими же координатами нельзя отличить друг от друга.
01
02
03
04
05
06
07
08
09
10
11
|
— (BOOL)isCurrentLocation:(NSDictionary *)location {
// Fetch Current Location
NSDictionary *currentLocation = [[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocation];
if ([location[MTLocationKeyLatitude] doubleValue] == [currentLocation[MTLocationKeyLatitude] doubleValue] &&
[location[MTLocationKeyLongitude] doubleValue] == [currentLocation[MTLocationKeyLongitude] doubleValue]) {
return YES;
}
return NO;
}
|
Когда пользователь нажимает кнопку удаления в строке представления таблицы, источнику данных представления таблицы отправляется сообщение tableView:commitEditingStyle:forRowAtIndexPath:
В этом методе нам нужно (1) обновить источник данных, (2) сохранить изменения в пользовательской базе данных по умолчанию и (3) обновить представление таблицы. Если editingStyle
равен UITableViewCellEditingStyleDelete
, мы удаляем местоположение из массива locations
и сохраняем обновленный массив в базе данных пользователя по умолчанию. Мы также удаляем строку из табличного представления, чтобы отразить изменение в источнике данных.
01
02
03
04
05
06
07
08
09
10
11
12
|
— (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Update Locations
[self.locations removeObjectAtIndex:(indexPath.row — 1)];
// Update User Defaults
[[NSUserDefaults standardUserDefaults] setObject:self.locations forKey:MTRainUserDefaultsLocations];
// Update Table View
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
}
|
Чтобы переключить стиль редактирования табличного представления, нам нужно добавить кнопку редактирования в пользовательский интерфейс. Создайте выход для кнопки в MTLocationsViewController.h и действие с именем editLocations:
в MTLocationsViewController.m . В editLocations:
мы переключаем стиль редактирования табличного представления.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
#import <UIKit/UIKit.h>
@protocol MTLocationsViewControllerDelegate;
@interface MTLocationsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) id<MTLocationsViewControllerDelegate> delegate;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *editButton;
@end
@protocol MTLocationsViewControllerDelegate <NSObject>
— (void)controllerShouldAddCurrentLocation:(MTLocationsViewController *)controller;
— (void)controller:(MTLocationsViewController *)controller didSelectLocation:(NSDictionary *)location;
@end
|
1
2
3
|
— (IBAction)editLocations:(id)sender {
[self.tableView setEditing:![self.tableView isEditing] animated:YES];
}
|
Откройте MTLocationsViewController.xib , добавьте панель навигации в представление контроллера представления и добавьте кнопку редактирования на панель навигации. Соедините кнопку редактирования с выходом и действием, которое мы создали минуту назад.
Вы можете быть удивлены, почему мы создали розетку для кнопки редактирования. Причина в том, что мы должны иметь возможность изменять заголовок кнопки редактирования с « Правка» на « Готово» и наоборот, когда меняется стиль редактирования табличного представления. Кроме того, когда пользователь удаляет последнее местоположение (кроме текущего местоположения) в табличном представлении, было бы хорошо автоматически переключать стиль редактирования табличного представления. Эти функции несложно реализовать, поэтому я оставляю их вам в качестве упражнения. Если у вас возникли проблемы или у вас есть вопросы, не стесняйтесь оставлять комментарии в комментариях под этой статьей.
Вывод
Мы успешно интегрировали API прогноза в наше приложение погоды. В следующем уроке мы сосредоточимся на пользовательском интерфейсе и дизайне приложения.