|
Май
15
|
Предполагается, что читатель уже имеет определенное представление о среде разработки приложений iPhone SDK (при необходимости можно скачать ее бесплатно отсюда http://developer.apple.com/iphone/). В этот раз наша задача — создать программу для чтения простой RSS-ленты (разумеется, это будет The Apple Blog).
Начинаем.
- Откройте Xcode, перейдите в меню “File” и щелкните на элементе “New Project…“.
- В списке слева под меню “iPhone OS” щелкните на “Application“.
- Справа выберите “Navigation-Based Application“, затем воспользуйтесь кнопкой “Choose…“. Теперь нужно выбрать имя и путь. Наберите название “TAB RSS reader“.
- Сохраните проект, куда сочтете нужным.
На экране появится окно проекта Xcode с тремя стандартными панелями. Советую сместить находящийся справа горизонтальный разделитель до конца вверх — дополнительное пространство для редактора будет отнюдь не лишним.
Видите кнопку “Build and Go” на панели инструментов? Щелкните на ней или откройте меню “Build“, выполнив потом “Build and Go (Run)“. Результатом должна стать запущенная программа-симулятор с простейшим приложением в iPhone — пустая панель навигации и пустая таблица. Вот и первый собственный проект для iPhone! Теперь нужно придать ему форму.
В предложенном Apple шаблоне проекта многое уже настроено. Найдите слева в окне список и дважды щелкните на элементе “MainWindow.xib“. Это базовый фрейм пользовательского интерфейса приложения. От него нам нужно только одно: найти окно “Navigation Controller” с макетом базового интерфейса, дважды щелкнуть по панели навигации (безымянной) и ввести “The Apple Blog“. Подтвердите клавишей <return>, сохранитесь и закройте средство Interface Builder.
Щелкнув на элементе списка “RootViewController.h“, обратите внимание на код справа. Ваша задача — придать ему следующий вид:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @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-ленте) и помещать их в массив. Следовательно, метод сообщает таблице: Вот сколько рядов необходимо — по одному на каждый элемент массива или на элемент ленты. Указанное прежде значение равнялось нулю, теперь по массиву задано больше информации.
Следующим шагом откорректируем находящийся ниже по списку метод:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | - (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-ленте) и помещать их в массив. Следовательно, метод сообщает таблице: Вот сколько рядов необходимо — по одному на каждый элемент массива или на элемент ленты. Указанное прежде значение равнялось нулю, теперь по массиву задано больше информации.
Следующим шагом откорректируем находящийся ниже по списку метод:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | - (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; } |
С помощью метода “setText:” мы сообщили ячейке о ее будущем содержимом. Каждая строка в таблице базово является ячейкой, а данный метод задает ее свойства.
Прокрутив бегунок вниз почти до конца, можно увидеть еще 4 выделенных зеленым цветом метода. При желании их можно удалить: они управляют добавлением/удалением элементов и нам не понадобятся.
Если попробовать запустить сейчас приложение, функционировать оно пока не будет: мы не добавили возможность загружать ленту и работать с ней. Этим и займемся.
Отредактируйте метод “viewDidAppear:“, придав ему следующий вид:
1 2 3 4 5 6 7 8 9 10 | - (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); } |
Здесь мы сообщаем анализатору, какую ленту загружать. Он обращается к методу, который теперь пора вставить:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | - (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-ленту. В процессе работы анализатора контроллер, который пришла пора добавить, будет получать различные методы делегирования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | - (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”, он сохраняет статью, очищает поля и переходит к следующему элементу, пока не доберется до конца файла. Не лучший способ, но работает.
Завершающий этап.
Стоит сразу же предотвратить любую потенциальную утечку памяти (хорошая привычка для тех, кто не коллекционирует информационный мусор). Отредактируем код:
1 2 3 4 5 6 7 8 9 10 11 12 | - (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 — это просто. Слегка изменим метод:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | - (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“, убедившись, что все работает.






Июнь 23rd, 2009 at 14:55
Вот этот кусок текста и листинг под ним повторяется два раза:
В декларациях мы сослались на массив (NSMutableArray – модифицируемая подборка объектов) который мы назвали “stories” (статьи). Обрамляющие выражение сигнализируют о сообщении: мы запрашиваем массив статей-stories о его количественном значении — т.е. интересуемся, сколько в нем объектов. Наше приложение для RSS-лент будет захватывать как можно больше элементов (по одному для каждой статьи в RSS-ленте) и помещать их в массив. Следовательно, метод сообщает таблице: Вот сколько рядов необходимо — по одному на каждый элемент массива или на элемент ленты. Указанное прежде значение равнялось нулю, теперь по массиву задано больше информации.
Следующим шагом откорректируем находящийся ниже по списку метод:
Июль 1st, 2009 at 08:45
Видел что-то подобное в буржунете, в Рунете про такое как-то не особо часто сообщения увидишь.
Июль 21st, 2009 at 20:56
В принципе многое я понял, но остался 1 вопрос и 1 проблема.
Вопрос: как запустить программу на iPhone?
Проблема: скопировал, изменил код, чтобы ссылки открывались в Safari, всё сделал как надо, но ссылки всё равно не открываются.
Июль 26th, 2009 at 13:11
Мне ответит кто нибудь?
Июль 26th, 2009 at 15:19
До сих пор не разобрались да? Ждем знатока
Июль 29th, 2009 at 22:41
А кто знаток? Разве не вы?
P.S. Я вас прошу пооперативней отвечать на комментарии, больно уж долго
Июль 30th, 2009 at 04:16
Я повторяю еще раз. Я не программист, и эти уроки не моего авторства. Ссылка на оригинал есть в конце каждой статьи. Почитайте комменты там, может поможет. Если не можете разобраться то попробуйте изучить другой урок, их уже более 80-ти штук, есть что выбрать.
Июль 30th, 2009 at 09:23
Ладно
Сентябрь 21st, 2009 at 09:56
Artem! Спасибо за статью, а возможно и за перевод! Тут ты продублировал просто Абзац, указанный выше aк’ом - а так вроде все отлично!
Октябрь 10th, 2011 at 15:41
Посетите наш Hi-Tech портал Беларусь. здесь вы всегда найдете свежие ИТ новости про компьютеры и ноутбуки в Беларуси, а также скачать без регистрации бесплатные программы, soft . участвуйте в жизни интернет ресурса, присылайте интересные статьи, обзоры - мы их обязательно опубликуем. Участвуйте в опросах и голосованиях , находя лучшее. Если у Вас есть сайт ИТ тематики, то регистрируйте его в нашем каталоге-рейтинге сайтов ИТ тематики. ИТ новости
Декабрь 28th, 2011 at 06:02
bristol plumbers
Январь 12th, 2012 at 13:32
Код не открывает страницы по простой причине - кривого копипейста. В блоке очиски ссылки от пробелов и прочего мусора третьей строкой идет очистка от табуляции, а там, вместо табуляции, стоит 2 пробела. Вы бы глянули в лог - было бы видно. Речь об вот этом:
// очистить ссылку от пробелов, знаков абзаца и табуляции…
storyLink = [storyLink stringByReplacingOccurrencesOfString:@" " withString:@""];
storyLink = [storyLink stringByReplacingOccurrencesOfString:@"\n" withString:@""];
storyLink = [storyLink stringByReplacingOccurrencesOfString:@"ЗДЕСЬ_НАДО_ТАВ" withString:@""];
и все работает