Рассмотрим пример наполнения UITableView данными из Интернета. Данная задача встречается очень часто. Будем рассматривать в контексте написания RSS-читалки. Для того, чтобы полностью понять материал вам нужны знания о UITableView и парсинге XML. Эти обе темы затрагивались на этом сайте:

  • Все о UITableView
  • Парсинг XML в Objective-C

Начало

Начнем с того, что нам понадобиться. Загрузите исходный проект из статьи «Парсинг XML в Objective-C» (можно по этой ссылке). Далее создадим проект «Single View Application»:

Добавим в проект два файла из исходников:

  • ParserDelegate.h
  • ParserDelegate.m

Переименуем данный класс в AppModel. Для этого откроем ParserDelegate.h нажмем правой кнопкой мыши на заголовок класса и выберем Refactor -> Rename:

В появившемся окне введем AppModel и применим изменения. Теперь файлы называются AppModel.h и AppModel.m.

Небольшие изменения

Класс AppModel должен уметь загружать данные, парсить их и уведомлять о начале и окончании процесса. Для этого нам нужен протокол взаимодействия (интерфес). Протокол будет простой — в нем будет описано только начало и окончание процесса. Добавим в файл AppModel.h (между #import и @interface):

@class AppModel;

@protocol AppModelDelegate <nsobject>
// уведомляет делегат о начале парсинга
-(void) appModelParsingDidStart:(AppModel*)appModel;
// уведомляет делегат о завершении парсинга
-(void) appModelParsingDidFinish:(AppModel*)appModel;
@end

Мы определили протокол. Далее следует определить свойство делегата, который будет получать уведомления (добавьте в файл AppModel.h там, где и другие свойства):

@property (retain) id<appModelDelegate> delegate;

и не забудьте синтезировать (в AppModel.m после @implementation):

@synthesize delegate;

Теперь класс AppModel поддерживает делегат, который будет получать уведомления о событиях.

И еще изменения

Теперь нужно добавить парсер в класс AppModel. Добавим парсер в класс и метод refresh, теперь его объявление будет выглядеть так:

@interface AppModel : NSObject<nsxmlparserDelegate> {

BOOL m_done;
BOOL m_isTitle;
NSError* m_error;
NSMutableArray* m_titles;
NSMutableString* m_title;
NSXMLParser* m_parser;
}
@property (retain) id<appModelDelegate> delegate;
// свойство-флаг, который показывает закончен ли парсинг
@property (readonly) BOOL done;
// если есть ошибка - ее описание, если нет - nil
@property (readonly) NSError* error;
// результат парсинга
@property (readonly) NSArray* titles;
-(void) refresh;
@end

Реализуем метод refresh, который в свою очередь будет вызывать в фоновом потоке парсер (чтобы не тормозить главный поток загрузкой из интернета):

-(void) refresh {

[self performSelectorInBackground:@selector(runParserInBackground) withObject:nil];
}
-(void) runParserInBackground {
NSAutoreleasePool* pool = [NSAutoreleasePool new];
[m_parser release];
m_parser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:@"http://idev.by/feed/"]];
[m_parser setDelegate:self];
[m_parser parse];
[pool drain];
}

Метод refresh нужно вызывать всякий раз, если мы хотим обновить ленту либо загрузить ее первый раз.

Теперь добавим методы, которые нужно вызывать внутри класса, чтобы оповестить делегат:

-(void) parserDidStart {

if ( [self.delegate respondsToSelector:@selector(appModelParsingDidStart:)] )
[self.delegate appModelParsingDidStart:self];
}
-(void) parserDidFinish {
if ( [self.delegate respondsToSelector:@selector(appModelParsingDidFinish:)] )
[self.delegate appModelParsingDidFinish:self];
}

Обратите внимание на то, что мы проверяем перед вызовом поддерживает ли делегат конкретный метод.

И последним шагом будет добавление вызова уведомлений. Изменим методы refresh и parserDidEndDocument: :

-(void) refresh {

[self parserDidStart];
[self performSelectorInBackground:@selector(runParserInBackground) withObject:nil];
}
// парсинг окончен
- (void)parserDidEndDocument:(NSXMLParser *)parser {
m_done = YES;
// вызовем метод в главном потоке, т.к. парсер работает в фоновом
[self performSelectorOnMainThread:@selector(parserDidFinish) withObject:nil waitUntilDone:NO];
}

Добавим UITableView

Ну вот, большинство приготовлений закончено. Откроем ViewController.xib и добавим на него UITableView, определим для UITableView dataSource и delegate на ViewController, а так же определим UITableView как свойство у ViewController. Как это сделать написано тут (Ищите Assistant).

Готовим ViewController

Нужно указать, что наш ViewController следует протоколам UITableViewDelegate, UITableViewDataSource, AppModelDelegate:

@interface ViewController : UIViewController<uitableViewDelegate,UITableViewDataSource,AppModelDelegate> 

а так же подключить в ViewController.h файл AppModel.h :

#import "AppModel.h"

и добавить в ViewController AppModel. Все объявление ViewController:

@interface ViewController : UIViewController<uitableViewDelegate,UITableViewDataSource,AppModelDelegate> {

AppModel* m_appModel;
}
@property (retain, nonatomic) IBOutlet UITableView *tableView;
@end

Теперь нужно добавить создание AppModel при создании ViewController (замените в ViewController.m):

-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {

self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
m_appModel = [AppModel new];
[m_appModel setDelegate:self];
return self;
}

Реализуем методы делегата для приема событий:

-(void) appModelParsingDidStart:(AppModel*)appModel {

// показываем индикатор загрузки при старте
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
-(void) appModelParsingDidFinish:(AppModel*)appModel {
// убираем индикатор при окончании
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
// перезагружаем таблицу при поступлении данных
[self.tableView reloadData];
}

Сделаем, чтобы после загрузки UIView в ViewController запускалась загрузка данных:

- (void)viewDidLoad

{
[super viewDidLoad];
[m_appModel refresh];
}

Осталось реализовать методы, которые требует UITableView:

-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

if ( m_appModel )
return m_appModel.titles.count;
else
return 0;
}
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell;
static NSString* cellID = @"cellID";
cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:cellID];
if ( !cell ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID] autorelease];
}
NSString* title = [m_appModel.titles objectAtIndex:indexPath.row];
cell.textLabel.text = title;
return cell;
}

Первый метод проверяет создан ли вообще AppModel, и если да то возвращает количество загруженных заголовков. А если не создан — возвращает ноль. Второй метод создает ячейку и задает ей в качестве текста конкретный заголовок.

На этом все. На самом деле мы не реализовали RSS-читалку в полном понимании. Это будет компенсировано второй статьей, которую я постараюсь написать как можно скорее. Если что-то осталось непонятным или есть вопросы — смело задавайте в комментариях либо на форуме.

Исходники вы можете загрузить тут

Последние статьи

  • Audio Unit в iOS. Часть 3, накладываем эффект Delay

  • Audio Unit в iOS. Часть 2, строим граф и проигрываем файлы

  • Audio Unit в iOS. Часть 1, введение.

  • Используем Emoji в своих приложениях

  • Как вводить в UITextField только цифры?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *