Главная iPhone Mac OS X Форум О себе

Создание простой программы для чтения RSS-лент

В этот раз наша задача — создать программу для чтения простой RSS-ленты (разумеется, это будет The Apple Blog).

Начинаем.

  1. Откройте Xcode, перейдите в меню “File” и щелкните на элементе “New Project…“.
  2. В списке слева под меню “iPhone OS” щелкните на “Application“.
  3. Справа выберите “Navigation-Based Application“, затем воспользуйтесь кнопкой “Choose…“. Теперь нужно выбрать имя и путь. Наберите название “TAB RSS reader“.
  4. Сохраните проект, куда сочтете нужным.

На экране появится окно проекта Xcode с тремя стандартными панелями. Советую сместить находящийся справа горизонтальный разделитель до конца вверх — дополнительное пространство для редактора будет отнюдь не лишним.

Видите кнопку “Build and Go” на панели инструментов? Щелкните на ней или откройте меню “Build“, выполнив потом “Build and Go (Run)“. Результатом должна стать запущенная программа-симулятор с простейшим приложением в iPhone — пустая панель навигации и пустая таблица. Вот и первый собственный проект для iPhone! Теперь нужно придать ему форму.

В предложенном Apple шаблоне проекта многое уже настроено. Найдите слева в окне список и дважды щелкните на элементе “MainWindow.xib“. Это базовый фрейм пользовательского интерфейса приложения. От него нам нужно только одно: найти окно “Navigation Controller” с макетом базового интерфейса, дважды щелкнуть по панели навигации (безымянной) и ввести “The Apple Blog“. Подтвердите клавишей <return>, сохранитесь и закройте средство Interface Builder.
Щелкнув на элементе списка “RootViewController.h“, обратите внимание на код справа. Ваша задача — придать ему следующий вид:


@interface RootViewController : UITableViewController {
    IBOutlet UITableView * newsTable;
    UIActivityIndicatorView * activityIndicator;
    CGSize cellSize;
    NSXMLParser * rssParser;
    NSMutableArray * stories;
    
    // временный элемент; добавляется к массиву "stories" по одному, и удаляется перед появлением следующего
    NSMutableDictionary * item;
    
    // анализирует документ, от начала до конца...
    // мы собираем и помещаем в оперативную память каждое значение под-элемента, потом сохраняем каждый элемент в массив.
    // тем самым каждый текущий элемент отслеживается до тех пор, пока он не будет готов к добавлению в массив "статей"
    NSString * currentElement;
    NSMutableString * currentTitle, * currentDate, * currentSummary, * currentLink;
}
@end

Это файл декларации, сообщающий компилятору, чего ожидать от логики контроллера. Именно здесь и разворачиваются реальные события… Откройте “RootViewController.m“.

Содержащийся здесь исходный код управляет отображением таблиц — данный контроллер “представляет их интересы”: таблица обращается к нему, чтобы выяснить, что выводить на экран / отображать в той или иной ситуации, отправляет запросы на методы при выполнении пользователем различных действий.

Меняем значение с — (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section, на return [stories count];

В декларациях мы сослались на массив (NSMutableArray – модифицируемая подборка объектов) который мы назвали “stories” (статьи). Обрамляющие выражение квадратные скобки сигнализируют о сообщении: мы запрашиваем массив статей-stories о его количественном значении — т.е. интересуемся, сколько в нем объектов. Наше приложение для RSS-лент будет захватывать как можно больше элементов (по одному для каждой статьи в RSS-ленте) и помещать их в массив. Следовательно, метод сообщает таблице: Вот сколько рядов необходимо — по одному на каждый элемент массива или на элемент ленты. Указанное прежде значение равнялось нулю, теперь по массиву задано больше информации.

Следующим шагом откорректируем находящийся ниже по списку метод:


— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *MyIdentifier = @"MyIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
    
    if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
    }
    
    // Настройка ячейки
    int storyIndex = [indexPath indexAtPosition: [indexPath length] — 1];
    [cell setText:[[stories objectAtIndex: storyIndex] objectForKey: @"title"]];
    
    return cell;
}

В декларациях мы сослались на массив (NSMutableArray – модифицируемая подборка объектов) который мы назвали “stories” (статьи). Обрамляющие выражение сигнализируют о сообщении: мы запрашиваем массив статей-stories о его количественном значении — т.е. интересуемся, сколько в нем объектов. Наше приложение для RSS-лент будет захватывать как можно больше элементов (по одному для каждой статьи в RSS-ленте) и помещать их в массив. Следовательно, метод сообщает таблице: Вот сколько рядов необходимо — по одному на каждый элемент массива или на элемент ленты. Указанное прежде значение равнялось нулю, теперь по массиву задано больше информации.

С помощью метода “setText:” мы сообщили ячейке о ее будущем содержимом. Каждая строка в таблице базово является ячейкой, а данный метод задает ее свойства.

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

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

Отредактируйте метод “viewDidAppear:“, придав ему следующий вид:


— (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    if ([stories count] == 0) {
        NSString * path = @"http://feeds.feedburner.com/TheAppleBlog";
        [self parseXMLFileAtURL:path];
    }
    
    cellSize = CGSizeMake([newsTable bounds].size.width, 60);
}

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


— (void)parseXMLFileAtURL:(NSString *)URL {
    stories = [[NSMutableArray alloc] init];
    
    //необходимо преобразовать путь в нужный NSURL, иначе приложение не будет работать
    NSURL *xmlURL = [NSURL URLWithString:URL];
    
    // здесь по определенным причинам нужно использовать NSClassFromString для назначения NSXMLParser, иначе появится сообщение об ошибке при попытке отыскать объект
    // понадобится только для toolchain
    rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
    
    // Устанавливает себя делегатом анализатора для обратной связи с его методами делегирования.
    [rssParser setDelegate:self];
    
    // В зависимости от анализируемого XML-документа, для NSXMLParser могут оказаться востребованными следующие функции.
    [rssParser setShouldProcessNamespaces:NO];
    [rssParser setShouldReportNamespacePrefixes:NO];
    [rssParser setShouldResolveExternalEntities:NO];
    
    [rssParser parse];
}

Добавленный метод создает пустой массив для статей, анализатор и начинает загружать RSS-ленту. В процессе работы анализатора контроллер, который пришла пора добавить, будет получать различные методы делегирования:


— (void)parserDidStartDocument:(NSXMLParser *)parser {
    NSLog(@"found file and started parsing");
}

— (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSString * errorString = [NSString stringWithFormat:@"Unable to download story feed from web site (Error code %i )", [parseError code]];
    NSLog(@"error parsing XML: %@", errorString);
    
    UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:@"Error loading content" message:errorString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [errorAlert show];
}

— (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
    //NSLog(@"нашел такой элемент: %@", elementName);
    currentElement = [elementName copy];

    if ([elementName isEqualToString:@"item"]) {
        // очистить кэш для статьи...
        item = [[NSMutableDictionary alloc] init];
        currentTitle = [[NSMutableString alloc] init];
        currentDate = [[NSMutableString alloc] init];
        currentSummary = [[NSMutableString alloc] init];
        currentLink = [[NSMutableString alloc] init];
    }
}

— (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{

    //NSLog(@"ended element: %@", elementName);
    if ([elementName isEqualToString:@"item"]) {
        // сохранить значения для элемента, затем сохранить элемент в массив...
        [item setObject:currentTitle forKey:@"title"];
        [item setObject:currentLink forKey:@"link"];
        [item setObject:currentSummary forKey:@"summary"];
        [item setObject:currentDate forKey:@"date"];
        
        [stories addObject:[item copy]];
        NSLog(@"adding story: %@", currentTitle);
    }
}

— (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
    //NSLog(@"found characters: %@", string);
    // сохранить символы для текущего элемента...
    if ([currentElement isEqualToString:@"title"]) {
        [currentTitle appendString:string];
    } else if ([currentElement isEqualToString:@"link"]) {
        [currentLink appendString:string];
    } else if ([currentElement isEqualToString:@"description"]) {
        [currentSummary appendString:string];
    } else if ([currentElement isEqualToString:@"pubDate"]) {
        [currentDate appendString:string];
    }
}

— (void)parserDidEndDocument:(NSXMLParser *)parser {
    
    [activityIndicator stopAnimating];
    [activityIndicator removeFromSuperview];
    
    NSLog(@"all done!");
    NSLog(@"stories array has %d items", [stories count]);
    [newsTable reloadData];
}

К сожалению, NSXMLParser — единственный доступный в iPhone простой инструмент XML-парсинга (так не хватает любимых Mac-средств). Это означает, что нам придется перебрать по порядку весь файл сверху донизу. Имеющемуся набору символов присваиваем значения, а затем собираем в поочередно сохраняемые элементы-статьи. Отыскав замыкающий тэг “item”, он сохраняет статью, очищает поля и переходит к следующему элементу, пока не доберется до конца файла. Не лучший способ, но работает.

Завершающий этап.

Стоит сразу же предотвратить любую потенциальную утечку памяти (хорошая привычка для тех, кто не коллекционирует информационный мусор). Отредактируем код:


— (void)dealloc {
    [currentElement release];
    [rssParser release];
    [stories release];
    [item release];
    [currentTitle release];
    [currentDate release];
    [currentSummary release];
    [currentLink release];
    
    [super dealloc];
}

Теперь откройте “RootViewController.xib“. Удерживая нажатой клавишу <control>, перетащите пиктограмму-кубик из “RootViewController” в меню Table View. Появится список с тремя элементами, в котором нужно щелкнуть на опции “newsTable“. Сохранитесь и закройте Interface Builder.

Build and Go.

Щелкнув на меню “Build and Go“, просмотрите итоговый результат. Если запустить приложение не в симуляторе, а на iPhone, картина будет несколько другой: аппаратное обеспечение медленнее, и RSS-лента в EDGE будет грузиться очень долго. Тем не менее, работает! Но работает пока не все: при выборе элемента в таблице ничего не происходит (этот сценарий задан по умолчанию). Предлагаю открывать статьи в Safari — это просто. Слегка изменим метод:


— (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Навигационная логика
    
    int storyIndex = [indexPath indexAtPosition: [indexPath length] — 1];
    
    NSString * storyLink = [[stories objectAtIndex: storyIndex] objectForKey: @"link"];
    
    // очистить ссылку от пробелов, знаков абзаца и табуляции...
    storyLink = [storyLink stringByReplacingOccurrencesOfString:@" " withString:@""];
    storyLink = [storyLink stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    storyLink = [storyLink stringByReplacingOccurrencesOfString:@"  " withString:@""];
    
    NSLog(@"link: %@", storyLink);
    // открыть в Safari
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:storyLink]];
}

Теперь можно еще раз щелкнуть на “Build and Go“, убедившись, что все работает.

Скачать архив (15Кб)

Оригинальная статья theappleblog.com
Источник lookapp.ru

Комментарий

Комментарии

Василий 26.06.2009 13:32

Наконец-то статьи пошли) Уря!!!!

ответить
LbICbIY 26.06.2009 14:41

+1 :) Давно ждал возрождения

ответить
AlD 26.06.2009 14:59

Нее, в сафари статьи открывать это ужасно неудобно. Зачем тогда все это? Когда есть Apple созданная онлайн rss читалка для айфона? Глупо.

ответить
taptap 26.06.2009 15:38

Относитесь к этому проще, не воспринимайте статью в прямом смысле. Этот пост лишь еще один урок, благодаря которому вы учитесь на конкретном примере, а не на тухлом и неинтересном рисовании foo и bar. Да, это приложение мало юзабельно, но благодаря этому каждый может взять его за основу и улучшить, написав свой вариант программы.

ответить
Vitkovsky 27.06.2009 1:57

Ура Шеф вернулся:-)

ответить
Sosniuk 27.06.2009 21:15

Спасибо, что вернулись!!!

ответить
Sof. Bix 7.07.2009 10:40

А вы не задумывались откуда всякая фигня берется в storyLink? На мой взгляд на лицо серьезная бага, которая может выйти за пределы [NSCharacterSet whitespaceAndNewlineCharacterSet]. Она тоже у меня была, могу подсказать как исправить, но гораздо приятнее самому дойти, это поможет понять суть работы NSXMLParser. Кстати, строчку лучше всего очищать с помощью:

NSString * st = [bugLine stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

ответить

Форма комментирования для «Создание простой программы для чтения RSS-лент»

Обязательное поле. Не больше 30 символов.

Обязательное поле

captcha image Пожалуйста, введите символы, которые вы видите на изображении

Constantine 2.06.2010 23:35

а нельзя было выложить исходный код целиком? Что-бы видно было наглядно а то как то непонятно

ответить
Sof. Bix 3.06.2010 11:30

Вот мой вариант, он достаточно прост (вся фишка в склейке текста в parser:foundCharacters: и в очистке self.currentElement = @”“;), как видете никаких хаков нет и нет никаких табуляций с переводами строк!!!

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    // Определяем новый тег (поле)
    self.currentElement = elementName;
    if ([elementName isEqualToString:@"item"]) // Это условие открытия новой строки данных
    {
        NSLog(@"[parser] start new item parse");
        NSMutableDictionary * newItem = [[NSMutableDictionary alloc] init];
        self.item = newItem;
        [newItem release];
    }
}

— (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:@"item"]) {
        [self updateElement: self.item];
        [data addObject: item]; // текущий элемент вставляем в набор данных как новую строку
    } else {
        self.currentElement = @""; // для того чтобы не нацеплять после окончания строки данных символов!
    }
}

— (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    if (!item) // это возможно при чтении атрибутов в начале RSS данных
        return;
    // определяем выходной тег и если он зарегестрирован в потомке то добавляем как поле текущей строки данных:
    NSString * tagName = [registredTagNames objectForKey: self.currentElement]; 
    if (tagName){
        NSMutableString * currentContent = [item objectForKey: tagName];
        if (currentContent) // существующее поле дополняем, не найденое добавляем!
            [currentContent appendString:string];
        else{
            NSMutableString * newString = [[NSMutableString alloc] initWithString: string];
            [item setObject: newString forKey: tagName ];
            [newString release];
        }
    }
}
ответить
Реклама: заказать диплом в Москве;Реклама: видеосъемка свадьбы;Реклама: Кит;Реклама: Это важно - входные двери от производителя.;Реклама: רופא שיניים 24 שעות
Карта Сайта