ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C часть 3

TBXML

Недавно мы научились самостоятельно парсить XML, а в этом примере я покажу, как сделать то же самое быстрее и проще с помощью специальной библиотеки. Однако, перед изучением данного урока я настоятельно рекомендую ещё раз обратить внимание на следующие уроки:

 

 

В прошлый раз мы парсили rss сайт iMaladec.com, в данном примере проделаем то же самое. Для начала создадим новый проект с шаблона, который содержит таблицу (Master-Detail Application), так как все данные мы будем помещать именно в неё. Проект я назвал RSSiMaladec.

 

Пример предполагает работу со специальным парсером TBXML. Скачайте исходные коды этого парсера и добавьте их в проект. Кроме этого, TBXML не работает без libz.dylib, следовательно, нужно добавить этот фреймверк в проект так же. Ну и конечно не забудьте про класс APIDownload, так как перед тем, как парсить данные нам нужно их получить (закачать с интернета). Как это делать подробно описано в примере APIDownload. При добавление класса APIDownload не забудьте, что он не поддерживает ARC, поэтому для него следут добавить исключение -fno-objc-arc, как это показано в уроке Automatic Reference Counting (ARC).

Если Вы всё правильно сделали структура программы будет выглядеть примерно так:

 

 

Добавим в интерфейс класса MasterViewController массив, в котором будут храниться статьи:

 

<code data-result="[object Object]">#import &lt;UIKit/UIKit.h&gt;

@class DetailViewController;

@interface MasterViewController : UITableViewController

@property (strong, nonatomic) NSMutableArray *rssNews;
@property (strong, nonatomic) DetailViewController *detailViewController;

@end</code>

 

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

 

<code data-result="[object Object]">#import "TBXML.h"
#import "APIDownload.h"</code>

 

Затем изменим метод — (void)viewDidLoad:

 

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = @"imaladec.com";
    [APIDownload downloadWithURL:@"http://imaladec.com/rss.php" delegate:self];
}</code>

 

Надеюсь ничего нового Вы тут не обнаружили. Здесь мы стартуем закачку RSS-ленты нашего любимого сайта. Далее следует метод — (void)APIDownload:(APIDownload*)request и на нём мы остановимся поподробнее, разобрав строку за строкой, так как он и наличие добавленных нами классов заменяет кучу методов из предыдущего примера!

 

<code data-result="[object Object]">- (void)APIDownload:(APIDownload*)request {
    NSError *error;
    TBXML *tbxml = [TBXML newTBXMLWithXMLData:request.downloadData error:&amp;error];
    if (error) {
        NSLog(@"Ошибка парсинга:%@", error.localizedDescription);
        return;
    }

    TBXMLElement *root = tbxml.rootXMLElement;
    if (!root) {
        NSLog(@"Ошибка чтения корня XML");
        return;
    }

    TBXMLElement *channel = [TBXML childElementNamed:@"channel" parentElement:root];
    if (channel) {
        self.rssNews = [NSMutableArray array];

        TBXMLElement *item = [TBXML childElementNamed:@"item" parentElement:channel];
        while (item) {
            TBXMLElement *title = [TBXML childElementNamed:@"title" parentElement:item];
            TBXMLElement *link = [TBXML childElementNamed:@"link" parentElement:item];
            TBXMLElement *description = [TBXML childElementNamed:@"description" parentElement:item];
            TBXMLElement *image = [TBXML childElementNamed:@"image" parentElement:item];
            TBXMLElement *date = [TBXML childElementNamed:@"pubDate" parentElement:item];
            NSDictionary *newsItem = [NSDictionary dictionaryWithObjectsAndKeys:
                                      [TBXML textForElement:title], @"title",
                                      [TBXML textForElement:link], @"link",
                                      [TBXML textForElement:description], @"desc",
                                      [TBXML textForElement:image], @"image",
                                      [TBXML textForElement:date], @"date",
                                      nil];
            [self.rssNews addObject:newsItem];

            item = [TBXML nextSiblingNamed:@"item" searchFromElement:item];
        }
    }

    [self.tableView reloadData];
}</code>

 

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

 

Теперь давайте взглянем на структуру самого XML. Она ничем не отличается от той, над которой мы работали в уроке Создаем свою читалку RSS.

 

<code data-result="[object Object]">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
         &lt;rss version="2.0"&gt;
                  &lt;channel&gt;
                           &lt;item&gt;
                                     &lt;title&gt;&lt;/title&gt;
                                     &lt;link&gt;&lt;/link&gt;
                                     &lt;image&gt;&lt;/image&gt;
                                     &lt;description&lt;/description&gt;
                                     &lt;pubDate&gt;&lt;/pubDate&gt;
                           &lt;/item&gt;

                           &lt;item&gt;
                                     …
                           &lt;/item&gt;
                           &lt;item&gt;
                                     &lt;title&gt;&lt;/title&gt;
                                     &lt;link&gt;&lt;/link&gt;
                                     &lt;image&gt;&lt;/image&gt;
                                     &lt;description&lt;/description&gt;
                                     &lt;pubDate&gt;&lt;/pubDate&gt;
                           &lt;/item&gt;
                  &lt;/channel&gt;
         &lt;/rss&gt;</code>

 

Чтобы получить значения каждого элемента — нам нужно попасть в тег channel:

 

<code data-result="[object Object]">TBXMLElement *channel = [TBXML childElementNamed:@"channel" parentElement:root];
if (channel) {</code>

 

В методе childElementNamed:parentElement: мы ищем тэг channel, который находиться в корне XML-файла и если находим, то переменной channel типа TBXMLElement  присваиваем его содержимое, которое так же проверяем его на валидность. И если оно не пустое начинаем — просматривать.

 

<code data-result="[object Object]">self.rssNews = [NSMutableArray array];
TBXMLElement *item = [TBXML childElementNamed:@"item" parentElement:channel];
while (item != nil) {
</code>

 

Тут мы создаём массив новостей, так как если компилятор по какой-то причине до этого места не дойдёт, то массив нет смысла создавать. Теперь методу childElementNamed:parentElement: мы передаём имя элемента, данные которого хотим получить. Поскольку элементов item у нас будет много — мы создаем цикл, в котором будем читать значение каждого элемента.

 

<code data-result="[object Object]">TBXMLElement *title = [TBXML childElementNamed:@"title" parentElement:item];
TBXMLElement *link = [TBXML childElementNamed:@"link" parentElement:item];
TBXMLElement *description = [TBXML childElementNamed:@"description" parentElement:item];
TBXMLElement *image = [TBXML childElementNamed:@"image" parentElement:item];
TBXMLElement *date = [TBXML childElementNamed:@"pubDate" parentElement:item];</code>

 

Для каждого вложеного элемента item мы создаем экземпляр класса TBXMLElement в котором хранится значение вложенного элемента. После того как все вложенные элементы созданы — мы помещаем их значения в словарь с соответствующими ключами:

 

<code data-result="[object Object]">NSDictionary *newsItem = [NSDictionary dictionaryWithObjectsAndKeys:
                          [TBXML textForElement:title], @"title",
                          [TBXML textForElement:link], @"link",
                          [TBXML textForElement:description], @"desc",
                          [TBXML textForElement:image], @"image",
                          [TBXML textForElement:date], @"date",
                          nil];</code>

 

Этот словарь, в свою очередь, добавляем в массив rssNews. Метод nextSiblingNamed:searchFromElement: переменной item устанавливает новое значение (следующего элемента в XML-файле). И так будет продолжаться, пока все айтемы не будут пройдены:

 

<code data-result="[object Object]">item = [TBXML nextSiblingNamed:@"item" searchFromElement:item];</code>

 

Как только компилятор не находит очередного item – это означает, что мы полностью прогнали наш XML. Самое время обновить таблицу, в которую будем помещать распарсеные данные.

 

<code data-result="[object Object]">[self.tableView reloadData];</code>

 

Теперь осталось только организовать вывод распарсенных данных в таблицу.

 

<code data-result="[object Object]">- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.rssNews.count;
}</code>

 

Количество строк  – это количество наших айтемов, а значит и количество словарей в массиве rssNews.

 

Ну и непосредственно вывод данных, с которым Вы уже знакомы:

 

<code data-result="[object Object]">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
                                       reuseIdentifier:CellIdentifier] autorelease];
    }

    NSDictionary *newsItem = [rssNews objectAtIndex:indexPath.row];

    cell.textLabel.text = [newsItem objectForKey:@"title"];
    cell.detailTextLabel.text = [newsItem objectForKey:@"date"];

    return cell;
}</code>

 

В данном проекте реализована не только возможность просмотра названий новостей и их дат, но и открытия полной новости в отдельном окне. Для этого подготовим класс DetailViewController, его интерфейс должен выглядеть так:

 

<code data-result="[object Object]">#import &lt;UIKit/UIKit.h&gt;

@interface DetailViewController : UIViewController

@property (strong, nonatomic) NSDictionary *newsItem;
@property (strong, nonatomic) IBOutlet UIWebView *web;

@end</code>

 

И соответственно его реализация:

 

<code data-result="[object Object]">#import "DetailViewController.h"

@implementation DetailViewController

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.title = [self.newsItem objectForKey:@"title"];

    NSString *desc = [self.newsItem objectForKey:@"desc"];
    desc = [desc stringByReplacingOccurrencesOfString:@"/upload-files/"
                                           withString:@"http://imaladec.com/upload-files/"];
    [self.web loadHTMLString:desc baseURL:nil];
}

@end</code>

 

Добавьте на XIB файл компонент UIWebView и свяжите его с переменной web. В реализации синтезируем переменные и загружаем вUIWebView полученные данные. Не пугайтесь метода stringByReplacingOccurrencesOfString:withString: он всего лишь заменяет одну подстроку другой, так как для корректного отображения картинки в его ссылке не хватает доменного имени.

 

Осталось только передать новому классу полученные данные:

 

<code data-result="[object Object]">- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (!self.detailViewController) {
        self.detailViewController = [DetailViewController new];
    }

    self.detailViewController.newsItem = [self.rssNews objectAtIndex:indexPath.row];
    [self.navigationController pushViewController:self.detailViewController animated:YES];
}</code>

 

Проект готов! И как видите всё очень просто.

 

 

Comments are closed.