Статьи

Создание приложения погоды с прогнозом — интеграция API

В первой статье этой серии мы заложили основу проекта, настроив проект и создав структуру приложения. В этой статье мы используем библиотеку AFNetworking для взаимодействия с Forecast API.


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

Если вы хотите следовать, вам понадобится ключ API прогноза. Вы можете получить ключ API, зарегистрировавшись разработчиком в Forecast . Регистрация бесплатна, поэтому я рекомендую вам воспользоваться услугой Прогноз погоды. Вы можете найти свой ключ API в нижней части панели инструментов (рисунок 1).

Создание приложения погоды с прогнозом - интеграция с прогнозом - получение ключа API
Рисунок 1: Получение вашего ключа API

Как я писал ранее в этой статье, мы будем использовать библиотеку AFNetworking для связи с Forecast API. При работе с AFNetworking существует несколько вариантов, но для обеспечения будущего приложения мы AFHTTPClient класс AFHTTPClient . Этот класс предназначен для использования веб-служб, таких как Forecast API. Несмотря на то, что мы будем иметь доступ только к одной конечной точке API, все же полезно использовать AFHTTPClient как вы узнаете через несколько минут.

Рекомендуется создать подкласс AFHTTPClient для каждого веб-сервиса. Поскольку мы уже добавили AFNetworking в наш проект в предыдущем уроке, мы можем сразу же начать создавать подклассы AFHTTPClient .

Создайте новый класс Objective C, назовите его MTForecastClient и сделайте его подклассом AFHTTPClient (рисунок 2).

Создание приложения погоды с прогнозом - интеграция прогноза - создание подклассов AFHTTPClient
Рисунок 2: Подклассы AFHTTPClient

Мы примем шаблон синглтона, чтобы было проще использовать класс 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 .

В 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/ / , Ключ API является частью URL, как вы можете видеть. Это может показаться небезопасным, и это действительно так. Для кого-то нетрудно получить ключ API, поэтому рекомендуется работать с прокси-сервером для маскирования ключа API. Поскольку этот подход немного сложнее, я не буду освещать этот аспект в этой серии.

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»;

Прежде чем мы продолжим, я хотел бы расширить класс 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 и автоматически создает объект из данных ответа, словарь в этом примере.


Вооруженный классом 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];
    }
}

Вы когда-нибудь задумывались, почему я использую так много вспомогательных методов в своем коде? Причина проста. Оборачивая функциональность в вспомогательные методы, очень легко повторно использовать код в разных местах проекта. Однако главное преимущество заключается в том, что он помогает бороться с дублированием кода. Дублирование кода — это то, что вы всегда должны стараться избегать, насколько это возможно. Еще одним преимуществом использования вспомогательных методов является то, что он делает ваш код более читабельным. Создав методы, которые делают одно, и предоставив правильно выбранное имя метода, легче быстро читать и обрабатывать ваш код.

Настало время использовать библиотеку 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.


Есть ряд библиотек, которые предоставляют эту функциональность, но мы будем придерживаться 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);
}

Прежде чем закончить этот пост, я хочу добавить две дополнительные функции: (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: как показано ниже.

Создание приложения погоды с прогнозом - интеграция с прогнозом - добавление кнопки обновления
Рисунок 3: Добавление кнопки «Обновить»
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 , добавьте панель навигации в представление контроллера представления и добавьте кнопку редактирования на панель навигации. Соедините кнопку редактирования с выходом и действием, которое мы создали минуту назад.

Создание приложения погоды с прогнозом - интеграция с прогнозом - добавление кнопки редактирования
Рисунок 4: Добавление кнопки редактирования

Вы можете быть удивлены, почему мы создали розетку для кнопки редактирования. Причина в том, что мы должны иметь возможность изменять заголовок кнопки редактирования с « Правка» на « Готово» и наоборот, когда меняется стиль редактирования табличного представления. Кроме того, когда пользователь удаляет последнее местоположение (кроме текущего местоположения) в табличном представлении, было бы хорошо автоматически переключать стиль редактирования табличного представления. Эти функции несложно реализовать, поэтому я оставляю их вам в качестве упражнения. Если у вас возникли проблемы или у вас есть вопросы, не стесняйтесь оставлять комментарии в комментариях под этой статьей.

Мы успешно интегрировали API прогноза в наше приложение погоды. В следующем уроке мы сосредоточимся на пользовательском интерфейсе и дизайне приложения.