В предыдущих уроках мы изучали основы API NSURLSession
. Есть еще одна особенность API NSURLSession
которую мы еще не NSURLSession
и NSURLSession
вне процесса. В следующих двух уроках я покажу вам, как создать очень простой клиент подкаста, который позволяет выполнять фоновые загрузки.
Вступление
Клиент подкаста, который мы собираемся создать, на самом деле не будет таким функциональным. Это позволит пользователю запрашивать в API поиска iTunes список подкастов, выбирать подкасты и загружать эпизоды. Поскольку мы сосредоточены на NSURLSession
API, мы не будем вдаваться в эпизоды, загружаемые приложением.
Проект, однако, научит вас, как использовать задачи с данными и загружать задачи в реальном приложении. Клиент подкаста также разрешит фоновые загрузки, для которых мы будем использовать NSURLSession
API NSURLSession
. У нас немало дел, поэтому давайте не будем терять время и начнем.
1. Настройка проекта
Запустите Xcode 5, выберите « Создать»> «Проект …» в меню « Файл» и выберите шаблон приложения « Один вид» из списка шаблонов приложений iOS. Назовите приложение Singlecast , задайте для семейства устройств iPhone и скажите Xcode, где вы хотите сохранить проект. Нажмите Создать, чтобы создать проект.
2. Обновить раскадровку
Первое, что нам нужно сделать, это отредактировать основную раскадровку проекта. Откройте Main.storyboard , выберите единственный контроллер представления раскадровки и выберите « Встроить»> «Контроллер навигации» в меню « Редактор» . Причина встраивания контроллера представления в контроллер навигации станет ясна позже в этом руководстве.
3. Search View Controller
Шаг 1. Создание файлов классов
Как я уже упоминал во введении, для простоты пользователь сможет подписаться только на один подкаст. Давайте начнем с создания контроллера представления поиска. Выберите New> File … в меню File и выберите класс Objective C из опций справа. Назовите класс MTSearchViewController
и сделайте его подклассом UIViewController
. Оставьте флажок с меткой XIB для пользовательского интерфейса не отмеченным. Сообщите Xcode, где вы хотите сохранить файлы классов, и нажмите « Создать» .
Шаг 2: Обновить интерфейс класса
Прежде чем мы создадим пользовательский интерфейс, откройте файл заголовка контроллера представления и обновите интерфейс класса, как показано ниже. Мы указываем, что класс 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
|
Шаг 3: Создать пользовательский интерфейс
Пересмотрите основную раскадровку проекта и перетащите новый контроллер представления из библиотеки объектов справа. Выберите новый контроллер представления, откройте Identity Inspector справа и установите класс контроллера представления в MTSearchViewController
. С новым выбранным контроллером представления откройте меню « Редактор» и выберите « Встроить»> «Контроллер навигации» . Перетащите табличное представление в представление контроллера представления и соедините источник данных табличного представления и delegate
выходы с контроллером представления поиска .
При выбранном табличном представлении откройте инспектор атрибутов и установите количество ячеек прототипа равным 1
. Выберите ячейку прототипа и установите для ее свойства стиля значение Subtitle, а для идентификатора — значение SearchCell
.
Перетащите строку поиска из библиотеки объектов и добавьте ее в представление заголовка табличного представления. Выберите панель поиска и подключите ее delegate
розетки с контроллером представления.
Выберите контроллер представления и соедините его searchBar
и tableView
с панелью поиска и представлением таблицы соответственно. Есть несколько других вещей, которые нам нужно сделать, прежде чем мы закончим с раскадровкой.
Откройте библиотеку объектов и перетащите элемент панели кнопок на панель навигации. Выберите элемент кнопки панели, соедините его с действием cancel:
мы объявили в интерфейсе контроллера представления поиска, и измените его Идентификатор в Инспекторе атрибутов на Отмена .
Перетащите элемент кнопки панели на панель навигации контроллера представления (не контроллера поиска) и измените его Идентификатор в Инспекторе атрибутов на « Добавить» . Управляйте перетаскиванием от элемента панели кнопок к контроллеру навигации контроллера поиска и выберите модальное из всплывающего меню. Это создает переход от контроллера представления к контроллеру навигации контроллера представления поиска.
Шаг 4: Реализация табличного представления
Прежде чем мы реализуем протоколы 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.
Шаг 5: Создание сессии
Начнем с объявления двух дополнительных закрытых свойств в классе 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;
}
|
Шаг 6: Поиск
Чтобы ответить на ввод пользователя в строке поиска, мы реализуем 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];
}
|
Шаг 6: Выбор подкаста
Когда пользователь нажимает строку в табличном представлении для выбора подкаста, 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];
}
|
Шаг 7: Последние штрихи
Я хочу поговорить о двух деталях, прежде чем вернуться к классу 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
, и именно поэтому работает вышеуказанный подход. 4. Возвращение
Как мы видели ранее, мы сохраняем выбор пользователя в базе данных пользовательских настроек приложения. Нам нужно обновить класс 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»];
}
|
5. Извлечение и разбор корма
Шаг 1: Добавление зависимостей
Ответ, который мы получаем от API поиска iTunes, включает атрибут feedUrl
для каждого подкаста. Мы можем вручную получить канал и разобрать его. Однако, чтобы сэкономить время, мы будем использовать MWFeedParser , популярную библиотеку, которая может сделать это для нас. Вы можете вручную загрузить и включить библиотеку в свой проект, но я выберу Cocoapods . Я предпочитаю Cocoapods для управления зависимостями в проектах iOS и OS X. Вы можете прочитать больше о Cocoapods на его веб-сайте или на Mobiletuts + .
Выйдите из 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 для вас. Дважды щелкните этот файл, и вы должны быть готовы к работе.
Шаг 2: выборка и анализ канала
Откройте файл 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];
}
|
Шаг 3: Отображение канала
Прежде чем обновить пользовательский интерфейс, откройте 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 или на физическом устройстве и запустите его через все шаги. Теперь вы сможете искать подкасты, выбирать подкасты из списка и просматривать их эпизоды.
Вывод
В этом уроке мы многое сделали, но впереди у нас еще немало работы. В следующем уроке мы увеличим загрузку эпизодов из ленты и обсудим фоновые или внепроцессные загрузки. Будьте на связи.