Статьи

Работа с NSURLSession: часть 3

В предыдущих уроках мы изучали основы API NSURLSession . Есть еще одна особенность API NSURLSession которую мы еще не NSURLSession и NSURLSession вне процесса. В следующих двух уроках я покажу вам, как создать очень простой клиент подкаста, который позволяет выполнять фоновые загрузки.


Клиент подкаста, который мы собираемся создать, на самом деле не будет таким функциональным. Это позволит пользователю запрашивать в API поиска iTunes список подкастов, выбирать подкасты и загружать эпизоды. Поскольку мы сосредоточены на NSURLSession API, мы не будем вдаваться в эпизоды, загружаемые приложением.

Проект, однако, научит вас, как использовать задачи с данными и загружать задачи в реальном приложении. Клиент подкаста также разрешит фоновые загрузки, для которых мы будем использовать NSURLSession API NSURLSession . У нас немало дел, поэтому давайте не будем терять время и начнем.


Запустите Xcode 5, выберите « Создать»> «Проект …» в меню « Файл» и выберите шаблон приложения « Один вид» из списка шаблонов приложений iOS. Назовите приложение Singlecast , задайте для семейства устройств iPhone и скажите Xcode, где вы хотите сохранить проект. Нажмите Создать, чтобы создать проект.

Создайте проект в Xcode.

Настройте проект в Xcode.

Первое, что нам нужно сделать, это отредактировать основную раскадровку проекта. Откройте Main.storyboard , выберите единственный контроллер представления раскадровки и выберите « Встроить»> «Контроллер навигации» в меню « Редактор» . Причина встраивания контроллера представления в контроллер навигации станет ясна позже в этом руководстве.

Встроить контроллер представления в контроллер навигации.

Как я уже упоминал во введении, для простоты пользователь сможет подписаться только на один подкаст. Давайте начнем с создания контроллера представления поиска. Выберите New> File … в меню File и выберите класс Objective C из опций справа. Назовите класс MTSearchViewController и сделайте его подклассом UIViewController . Оставьте флажок с меткой XIB для пользовательского интерфейса не отмеченным. Сообщите Xcode, где вы хотите сохранить файлы классов, и нажмите « Создать» .

Создайте файлы для класса MTSearchViewController.

Прежде чем мы создадим пользовательский интерфейс, откройте файл заголовка контроллера представления и обновите интерфейс класса, как показано ниже. Мы указываем, что класс MTSearchViewController соответствует протоколам UITableViewDataSource , UITableViewDelegate и UISearchBarDelegate , мы объявляем два выхода, searchBar и tableView а также действие ( cancel для cancel контроллера представления поиска.

01
02
03
04
05
06
07
08
09
10
#import <UIKit/UIKit.h>
 
@interface MTSearchViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate>
 
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
 
— (IBAction)cancel:(id)sender;
 
@end

Пересмотрите основную раскадровку проекта и перетащите новый контроллер представления из библиотеки объектов справа. Выберите новый контроллер представления, откройте Identity Inspector справа и установите класс контроллера представления в MTSearchViewController . С новым выбранным контроллером представления откройте меню « Редактор» и выберите « Встроить»> «Контроллер навигации» . Перетащите табличное представление в представление контроллера представления и соедините источник данных табличного представления и delegate выходы с контроллером представления поиска .

Добавьте табличное представление в представление контроллера представления поиска и подключите его источник данных и делегируйте выходы.

При выбранном табличном представлении откройте инспектор атрибутов и установите количество ячеек прототипа равным 1 . Выберите ячейку прототипа и установите для ее свойства стиля значение Subtitle, а для идентификатора — значение SearchCell .

Создайте и настройте прототип ячейки.

Перетащите строку поиска из библиотеки объектов и добавьте ее в представление заголовка табличного представления. Выберите панель поиска и подключите ее delegate розетки с контроллером представления.

Добавьте панель поиска в представление заголовка табличного представления и подключите точку делегата.

Выберите контроллер представления и соедините его searchBar и tableView с панелью поиска и представлением таблицы соответственно. Есть несколько других вещей, которые нам нужно сделать, прежде чем мы закончим с раскадровкой.

Откройте библиотеку объектов и перетащите элемент панели кнопок на панель навигации. Выберите элемент кнопки панели, соедините его с действием cancel: мы объявили в интерфейсе контроллера представления поиска, и измените его Идентификатор в Инспекторе атрибутов на Отмена .

Добавьте элемент кнопки панели в контроллер представления поиска.

Перетащите элемент кнопки панели на панель навигации контроллера представления (не контроллера поиска) и измените его Идентификатор в Инспекторе атрибутов на « Добавить» . Управляйте перетаскиванием от элемента панели кнопок к контроллеру навигации контроллера поиска и выберите модальное из всплывающего меню. Это создает переход от контроллера представления к контроллеру навигации контроллера представления поиска.

Если бы вы управляли перетаскиванием из элемента кнопки панели контроллера представления непосредственно в контроллер представления поиска вместо его контроллера навигации, контроллер навигации никогда не был бы создан, и вы бы не увидели панель навигации в верхней части контроллера представления поиска.

Создайте переход от контроллера представления к контроллеру навигации контроллера представления поиска.

Прежде чем мы реализуем протоколы UITableViewDataSource и UITableViewDelegate в классе MTSearchViewController , нам нужно объявить свойство, в котором хранятся результаты поиска, которые мы получим из API-интерфейса поиска iTunes. Назовите podcasts свойств, как показано ниже. Мы также объявляем статическую строку, которая будет служить идентификатором повторного использования ячейки. Он соответствует идентификатору, который мы установили в ячейке прототипа несколько минут назад.

1
2
3
4
5
6
7
#import «MTSearchViewController.h»
 
@interface MTSearchViewController ()
 
@property (strong, nonatomic) NSMutableArray *podcasts;
 
@end
1
static NSString *SearchCell = @»SearchCell»;

Реализация numberOfSectionsInTableView: настолько проста, насколько это возможно. Мы возвращаем 1 если self.podcasts не nil и 0 если это так. Реализация tableView:numberOfRowsInSection: очень похожа, как вы можете видеть ниже. В tableView:cellForRowAtIndexPath: мы запрашиваем представление таблицы для ячейки, передавая идентификатор повторного использования ячейки, который мы объявили ранее, и indexPath . Мы выбираем соответствующий элемент из источника данных podcasts и обновляем ячейку табличного представления. И tableView:canEditRowAtIndexPath: и tableView:canMoveRowAtIndexPath: возвращают NO .

1
2
3
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.podcasts ?
}
1
2
3
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.podcasts ?
}
01
02
03
04
05
06
07
08
09
10
11
12
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SearchCell forIndexPath:indexPath];
 
    // Fetch Podcast
    NSDictionary *podcast = [self.podcasts objectAtIndex:indexPath.row];
 
    // Configure Table View Cell
    [cell.textLabel setText:[podcast objectForKey:@»collectionName»]];
    [cell.detailTextLabel setText:[podcast objectForKey:@»artistName»]];
 
    return cell;
}
1
2
3
— (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}
1
2
3
— (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}

Перед запуском приложения выполните действие cancel: в котором мы закрываем контроллер представления поиска.

1
2
3
— (IBAction)cancel:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}

Создайте проект и запустите приложение, чтобы убедиться, что фундамент работает должным образом. Пришло время начать использовать NSURLSession API для запроса iTunes Search API.

Начнем с объявления двух дополнительных закрытых свойств в классе MTSearchViewController : session и dataTask . Переменная session используется для хранения ссылки на экземпляр NSURLSession мы будем использовать для запроса API Apple. Мы также сохраняем ссылку на задачу данных, которую мы будем использовать для запроса. Это позволит нам отменить задачу обработки данных, если пользователь обновит поисковый запрос до того, как мы получим ответ от API. Если вам нужны подробности, вы могли заметить, что класс MTSearchViewController также соответствует протоколу UIScrollViewDelegate . Причина этого станет ясна через несколько минут.

01
02
03
04
05
06
07
08
09
10
#import «MTSearchViewController.h»
 
@interface MTSearchViewController () <UIScrollViewDelegate>
 
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSURLSessionDataTask *dataTask;
 
@property (strong, nonatomic) NSMutableArray *podcasts;
 
@end

Сессия создается в методе получения, как вы можете видеть ниже. Его реализация не должна содержать сюрпризов, если вы читали предыдущие уроки. Мы переопределяем метод получения свойства session чтобы лениво загрузить сеанс и ограничить создание и настройку сеанса в его методе получения. Это делает для чистого и элегантного кода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
— (NSURLSession *)session {
    if (!_session) {
        // Initialize Session Configuration
        NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
 
        // Configure Session Configuration
        [sessionConfiguration setHTTPAdditionalHeaders:@{ @»Accept» : @»application/json» }];
 
        // Initialize Session
        _session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
    }
 
    return _session;
}

Чтобы ответить на ввод пользователя в строке поиска, мы реализуем searchBar:textDidChange: протокола UISearchBarDelegate . Реализация проста. Если searchText равен nil , метод возвращается рано. Если длина searchText меньше четырех символов, мы сбрасываем поиск, вызывая resetSearch . Если запрос состоит из четырех или более символов, мы выполняем поиск, вызывая performSearch на контроллере представления поиска.

01
02
03
04
05
06
07
08
09
10
— (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    if (!searchText) return;
 
    if (searchText.length <= 3) {
        [self resetSearch];
 
    } else {
        [self performSearch];
    }
}

Прежде чем мы проверяем performSearch , давайте resetSearch рассмотрим resetSearch . Все, что мы делаем в resetSearch это очистка содержимого podcasts и перезагрузка табличного представления.

1
2
3
4
5
6
7
— (void)resetSearch {
    // Update Data Source
    [self.podcasts removeAllObjects];
 
    // Update Table View
    [self.tableView reloadData];
}

performSearch тяжестей выполняется в режиме « performSearch . После сохранения ввода пользователя в переменной с именем query , мы проверяем, установлен ли dataTask . Если он установлен, мы вызываем cancel на нем. Это важно, так как мы не хотим получать ответ от старого запроса, который больше не имеет отношения к пользователю. Это также причина того, что у нас есть только одна активная задача данных одновременно. Нет никаких преимуществ в отправке нескольких запросов к API.

Затем мы запрашиваем у сеанса новый экземпляр задачи с данными, передавая ему экземпляр NSURL и обработчик завершения. Помните, что сессия — это фабрика, которая создает задачи. Вы никогда не должны создавать задачи самостоятельно. Если мы получим правильную задачу данных из сеанса, мы вызовем ее resume как мы видели в предыдущих уроках.

Логика внутри обработчика завершения интересна, если не сказать больше. Объект error важен для нас по нескольким причинам. Он не только сообщит нам, если что-то пошло не так с запросом, но также будет полезен для определения отмены задачи с данными. Если мы получим объект ошибки, мы проверяем, равен ли его код ошибки -999 . Этот код ошибки указывает, что задача данных была отменена. Если мы получим другой код ошибки, мы зарегистрируем ошибку на консоли. В реальном приложении вам нужно улучшить обработку ошибок и уведомить пользователя о появлении ошибки.

Если в обработчик завершения не было передано никакой ошибки, мы создаем словарь из экземпляра NSData который был передан обработчику завершения, и извлекаем из него результаты. Если у нас есть массив результатов для работы, мы передаем его в processResults: Вы заметили, что мы processResults: в блоке GCD (Grand Central Dispatch)? Почему мы это сделали? Я надеюсь, вы помните, потому что это очень важная деталь. У нас нет гарантии, что обработчик завершения вызывается в основном потоке. Так как нам нужно обновить табличное представление в главном потоке, нам нужно убедиться, что processResults: вызывается в главном потоке.

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
— (void)performSearch {
    NSString *query = self.searchBar.text;
 
    if (self.dataTask) {
        [self.dataTask cancel];
    }
 
    self.dataTask = [self.session dataTaskWithURL:[self urlForQuery:query] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            if (error.code != -999) {
                NSLog(@»%@», error);
            }
 
        } else {
            NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
            NSArray *results = [result objectForKey:@»results»];
 
            dispatch_async(dispatch_get_main_queue(), ^{
                if (results) {
                    [self processResults:results];
                }
            });
        }
    }];
 
    if (self.dataTask) {
        [self.dataTask resume];
    }
}

Прежде чем мы рассмотрим реализацию processResults: я хочу быстро показать вам, что происходит в urlForQuery: вспомогательном методе, который мы используем в performSearch . В urlForQuery: мы заменяем любые пробелы знаком + чтобы гарантировать, что API поиска iTunes удовлетворяет тому, что мы отправляем. Затем мы создаем экземпляр NSURL и возвращаем его.

1
2
3
4
— (NSURL *)urlForQuery:(NSString *)query {
    query = [query stringByReplacingOccurrencesOfString:@» » withString:@»+»];
    return [NSURL URLWithString:[NSString stringWithFormat:@»https://itunes.apple.com/search?media=podcast&entity=podcast&term=%@», query]];
}

В processResults: переменная podcasts очищается, заполняется содержимым results , и результаты отображаются в табличном представлении.

01
02
03
04
05
06
07
08
09
10
11
12
— (void)processResults:(NSArray *)results {
    if (!self.podcasts) {
        self.podcasts = [NSMutableArray array];
    }
 
    // Update Data Source
    [self.podcasts removeAllObjects];
    [self.podcasts addObjectsFromArray:results];
 
    // Update Table View
    [self.tableView reloadData];
}

Когда пользователь нажимает строку в табличном представлении для выбора подкаста, tableView:didSelectRowAtIndexPath: протокола UITableViewDelegate . Вначале его реализация может показаться странной, поэтому позвольте мне объяснить, что происходит. Мы выбираем подкаст, который соответствует выбору пользователя, сохраняем его в базе данных по умолчанию для пользователя приложения и закрываем контроллер представления поиска. Мы никому не сообщаем об этом? Почему мы это делаем, станет ясно, как только мы продолжим реализацию класса MTViewController .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
— (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
 
    // Fetch Podcast
    NSDictionary *podcast = [self.podcasts objectAtIndex:indexPath.row];
 
    // Update User Defatuls
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [ud setObject:podcast forKey:@»MTPodcast»];
    [ud synchronize];
 
    // Dismiss View Controller
    [self dismissViewControllerAnimated:YES completion:nil];
}

Я хочу поговорить о двух деталях, прежде чем вернуться к классу MTViewController . Когда контроллер представления поиска представлен пользователю, становится ясно, что он хочет искать подкасты. Поэтому неплохо сразу представить клавиатуру. Мы делаем это в viewDidAppear: как показано ниже.

1
2
3
4
5
6
— (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
 
    // Show Keyboard
    [self.searchBar becomeFirstResponder];
}

Клавиатура должна скрывать момент, когда пользователь начинает просматривать результаты поиска. Для этого мы реализуем scrollViewDidScroll: протокола UIScrollViewDelegate . Это объясняет, почему MTSearchViewController соответствует протоколу UIScrollViewDelegate . Посмотрите на реализацию scrollViewDidScroll: показано ниже.

1
2
3
4
5
— (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if ([self.searchBar isFirstResponder]) {
        [self.searchBar resignFirstResponder];
    }
}
Класс UITableView является подклассом UIScrollView , и именно поэтому работает вышеуказанный подход.

Как мы видели ранее, мы сохраняем выбор пользователя в базе данных пользовательских настроек приложения. Нам нужно обновить класс MTViewController чтобы использовать выбор пользователя в контроллере представления поиска. В методе viewDidLoad контроллера viewDidLoad мы загружаем подкаст из базы данных пользовательских настроек по умолчанию и добавляем контроллер представления в качестве наблюдателя базы данных пользовательских настроек по умолчанию для ключевого пути MTPodcast чтобы контроллер представления MTPodcast уведомление при изменении значения для MTPodcast .

1
2
3
4
5
6
7
8
9
— (void)viewDidLoad {
    [super viewDidLoad];
 
    // Load Podcast
    [self loadPodcast];
 
    // Add Observer
    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@»MTPodcast» options:NSKeyValueObservingOptionNew context:NULL];
}

Все, что мы делаем в loadPodcast — это сохраняем значение MTPodcast из базы данных пользователя по умолчанию в свойстве podcast контроллера представления. Это значение будет равно nil если база данных пользователя по умолчанию не содержит записи для MTPodcast . Контроллер вида изящно справится с этим для нас. Помните, что в Objective-C вы можете отправлять сообщения на nil без всякого ада. Это имеет свои недостатки, но, безусловно, имеет свои преимущества.

1
2
3
4
— (void)loadPodcast {
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    self.podcast = [ud objectForKey:@»MTPodcast»];
}

Это также означает, что нам нужно объявить свойство с именем podcast в файле реализации контроллера представления.

1
2
3
4
5
6
7
#import «MTViewController.h»
 
@interface MTViewController ()
 
@property (strong, nonatomic) NSDictionary *podcast;
 
@end

Давайте также setPodcast: рассмотрим setPodcast: и updateView .

1
2
3
4
5
6
7
8
— (void)setPodcast:(NSDictionary *)podcast {
    if (_podcast != podcast) {
        _podcast = podcast;
 
        // Update View
        [self updateView];
    }
}
1
2
3
4
— (void)updateView {
    // Update View
    self.title = [self.podcast objectForKey:@»collectionName»];
}

Когда значение в пользовательской базе данных по умолчанию изменяется для ключа MTPodcast , контроллер представления может отреагировать на это изменение в observeValueForKeyPath:ofObject:change:context: Вот как работает наблюдение за ключевыми значениями. Все, что мы делаем в этом методе, это обновляем значение свойства podcast контроллера представления.

1
2
3
4
5
— (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@»MTPodcast»]) {
        self.podcast = [object objectForKey:@»MTPodcast»];
    }
}

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

1
2
3
— (void)dealloc {
    [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@»MTPodcast»];
}

Ответ, который мы получаем от API поиска iTunes, включает атрибут feedUrl для каждого подкаста. Мы можем вручную получить канал и разобрать его. Однако, чтобы сэкономить время, мы будем использовать MWFeedParser , популярную библиотеку, которая может сделать это для нас. Вы можете вручную загрузить и включить библиотеку в свой проект, но я выберу Cocoapods . Я предпочитаю Cocoapods для управления зависимостями в проектах iOS и OS X. Вы можете прочитать больше о Cocoapods на его веб-сайте или на Mobiletuts + .

Я собираюсь предположить, что драгоценный камень Cocoapods установлен в вашей системе. Вы можете найти подробные инструкции в этом руководстве .

Выйдите из Xcode, перейдите к корню вашего проекта XCode и создайте файл с именем Podfile . Откройте этот файл в текстовом редакторе и добавьте следующие три строки кода. В первой строке мы указываем платформу и цель развертывания, то есть iOS 7 в этом примере. Следующие две строки указывают на зависимость нашего проекта XCode. Первая — это библиотека MWFeedParser, и я также включил популярную библиотеку SVProgressHUD , которая пригодится чуть позже.

1
2
3
4
platform :ios, ‘7’
 
pod ‘MWFeedParser’
pod ‘SVProgressHUD’

Откройте окно терминала, перейдите к корню вашего проекта XCode и выполните команду pod install . Это должно установить зависимости и создать рабочее пространство Xcode. Когда Cocoapods закончит установку зависимостей проекта, он скажет вам использовать рабочее пространство, созданное для вас. Это важно, так что не игнорируйте этот совет. В корне вашего проекта Xcode вы увидите, что Cocoapods действительно создал рабочее пространство Xcode для вас. Дважды щелкните этот файл, и вы должны быть готовы к работе.

Установите зависимости проекта с помощью Cocoapods.

Откройте файл MTViewController класса MTViewController , добавьте оператор импорта для MWFeedParser и SVProgressHUD и объявите два свойства, episodes и feedParser . Нам также необходимо MTViewController соответствие протоколу MWFeedParserDelegate .

01
02
03
04
05
06
07
08
09
10
11
12
#import «MTViewController.h»
 
#import «MWFeedParser.h»
#import «SVProgressHUD.h»
 
@interface MTViewController () <MWFeedParserDelegate>
 
@property (strong, nonatomic) NSDictionary *podcast;
@property (strong, nonatomic) NSMutableArray *episodes;
@property (strong, nonatomic) MWFeedParser *feedParser;
 
@end

Далее, мы обновляем setPodcast: вызывая fetchAndParseFeed , вспомогательный метод, в котором мы используем класс MWFeedParser для выборки и анализа канала подкаста.

01
02
03
04
05
06
07
08
09
10
11
— (void)setPodcast:(NSDictionary *)podcast {
    if (_podcast != podcast) {
        _podcast = podcast;
 
        // Update View
        [self updateView];
 
        // Fetch and Parse Feed
        [self fetchAndParseFeed];
    }
}

В fetchAndParseFeed мы избавляемся от нашего текущего экземпляра MWFeedParser если он у нас есть, и инициализируем новый экземпляр с помощью URL-адреса канала подкаста. Мы устанавливаем свойство feedParseType в ParseTypeFull и устанавливаем контроллер представления в качестве делегата анализатора каналов. Перед тем, как получить канал, мы используем SVProgressHUD чтобы показать прогресс пользователя HUD.

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)fetchAndParseFeed {
    if (!self.podcast) return;
 
    NSURL *url = [NSURL URLWithString:[self.podcast objectForKey:@»feedUrl»]];
    if (!url) return;
 
    if (self.feedParser) {
        [self.feedParser stopParsing];
        [self.feedParser setDelegate:nil];
        [self setFeedParser:nil];
    }
 
    // Clear Episodes
    if (self.episodes) {
        [self setEpisodes:nil];
    }
 
    // Initialize Feed Parser
    self.feedParser = [[MWFeedParser alloc] initWithFeedURL:url];
 
    // Configure Feed Parser
    [self.feedParser setFeedParseType:ParseTypeFull];
    [self.feedParser setDelegate:self];
 
    // Show Progress HUD
    [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
 
    // Start Parsing
    [self.feedParser parse];
}

Нам также необходимо реализовать два метода протокола feedParser:didParseFeedItem: , feedParser:didParseFeedItem: и feedParserDidFinish: В feedParser:didParseFeedItem: мы инициализируем свойство episodes если необходимо, и передаем ему элемент ленты, который нам передает анализатор каналов.

1
2
3
4
5
6
7
— (void)feedParser:(MWFeedParser *)parser didParseFeedItem:(MWFeedItem *)item {
    if (!self.episodes) {
        self.episodes = [NSMutableArray array];
    }
 
    [self.episodes addObject:item];
}

В feedParserDidFinish: мы feedParserDidFinish: прогресс HUD и обновляем представление таблицы. Вы сказали, что просмотр таблицы? Это верно. Нам нужно добавить табличное представление и реализовать необходимые методы протокола UITableViewDataSource .

1
2
3
4
5
6
7
— (void)feedParserDidFinish:(MWFeedParser *)parser {
    // Dismiss Progress HUD
    [SVProgressHUD dismiss];
 
    // Update View
    [self.tableView reloadData];
}

Прежде чем обновить пользовательский интерфейс, откройте MTViewController.h , объявите выход для табличного представления и сообщите компилятору, что класс MTViewController соответствует протоколам UITableViewDataSource и UITableViewDelegate .

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
 
@interface MTViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
 
@property (weak, nonatomic) IBOutlet UITableView *tableView;
 
@end

Откройте основную раскадровку еще раз и добавьте табличное представление в представление контроллера представления. Соедините источник данных табличного представления и delegate выходы с контроллером представления и соедините выход tableView контроллера представления с представлением таблицы. Выберите представление таблицы, откройте инспектор атрибутов и установите количество ячеек прототипа равным 1 . Выберите ячейку прототипа, установите для нее стиль Subtitle и присвойте ей идентификатор EpisodeCell .

Добавление табличного представления в контроллер представления.

Прежде чем мы реализуем протокол UITableViewDataSource , объявите статическую строку с именем EpisodeCell в MTViewController.m . Это соответствует идентификатору, который мы установили для ячейки прототипа в раскадровке.

1
static NSString *EpisodeCell = @»EpisodeCell»;

Реализация протокола UITableViewDataSource очень проста и очень похожа на реализацию протокола в контроллере представления поиска. Единственное отличие состоит в том, что переменная episodes содержит экземпляры класса MWFeedItem вместо экземпляров NSDictionary .

1
2
3
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.episodes ?
}
1
2
3
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.episodes ?
}
01
02
03
04
05
06
07
08
09
10
11
12
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:EpisodeCell forIndexPath:indexPath];
 
    // Fetch Feed Item
    MWFeedItem *feedItem = [self.episodes objectAtIndex:indexPath.row];
 
    // Configure Table View Cell
    [cell.textLabel setText:feedItem.title];
    [cell.detailTextLabel setText:[NSString stringWithFormat:@»%@», feedItem.date]];
 
    return cell;
}
1
2
3
— (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}
1
2
3
— (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}

Запустите приложение в iOS Simulator или на физическом устройстве и запустите его через все шаги. Теперь вы сможете искать подкасты, выбирать подкасты из списка и просматривать их эпизоды.


В этом уроке мы многое сделали, но впереди у нас еще немало работы. В следующем уроке мы увеличим загрузку эпизодов из ленты и обсудим фоновые или внепроцессные загрузки. Будьте на связи.