Вездесущий UITableView
В сегодняшней статье мы с вами познакомимся и научимся работать с новым классом UITableView, научимся новым приемам в Interface Builder, найдем применение UINavigationController, освоим новые методы UIViewController и сделаем заготовку под новую версию текстового редактора, созданного нами в прошлом посте, для работы с несколькими документами.
В iPhone и iPod Touch основой очень многих приложений является Table View. Табличная форма удобна для представления разнообразного типа данных: начиная со списка контактов, дел в календаре, заканчивая плейлистами в плеере и списком альбомов в фотографиях. За отображение всех этих данных отвечает объект класса UITableView, знакомству с которым мы уделим сейчас наше внимание.
Знакомство с UITableView
Существует два способа работы с TableView — при помощи UITableViewController и через UIViewController. Воспользуемся последним, самым общим вариантом, отличающимся от первого лишь отсутствием некоторых обязательных элементов, реализацию которых мы напишем вручную, и поймем как работает UITableViewController изнутри. В своих программах вы уже будете вольны сами выбирать более удобный для вас способ.
Начнем наше знакомство с создания проекта под названием “tableView” в XCode на основе уже полюбившегося шаблона “Window-Based Application”. Откройте файл интерфейса, добавьте в форму UIViewController и поверх него UITableView. Как вы помните, UITableView является потомком UIView, поэтому в большинстве случаев вам не потребуется при добавлении его на форму использовать UIView в качестве основы, так как он будет занимать все свободное пространство в окне приложения.
UITableViewController отличается от нашего контроллера рядом методов и свойств, последние из которых мы объявим и свяжем в Interface Builder, а недостающую часть требований реализуем чуть позже в XCode. Для начала измените имя класса у объекта UIViewController на tableViewController в закладке Identity инспектора свойств.
- Наш контроллер должен иметь ссылку на содержащийся в нем TableView. Создайте новую Outlet переменную listView класса UITableView в инспекторе свойств для объекта tableViewController и свяжите ее с TableView.
- Делегатом и источником данных для TableView является его контроллер. Для этих целей у объекта TableView имеются предопределенные Outlet переменные delegate и dataSource. Свяжите их с контроллером tableViewController.
В итоге должна получиться следующая картина:
Сохраните все изменения в файле интерфейса, а также создайте и добавьте файлы, описывающие класс tableViewController, в состав нашего проекта (освежить память, если вы забыли как это делать, поможет Знакомство с Interface Builder). В заголовочном файле укажите родительский класс и список протоколов, которые реализует наш контроллер. Не забудьте определить свойство и синтезировать его для переменной listView, а также написать реализацию метода dealloc, где освобождаете память из-под переменной. В итоге файлы класса должны принять следующий вид:
tableViewController.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@interface tableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
IBOutlet UITableView *listView;
}
@property (nonatomic, retain) UITableView *listView;
@end
tableViewController.m
#import "tableViewController.h"
@implementation tableViewController
@synthesize listView;
-(void)dealloc {
[listView release];
[super dealloc];
}
@end
Также не забудьте создать Outlet переменную viewController в классе tableViewAppDelegate, связать ее с нашим контроллером через Interface Builder и добавить его view в окно приложения в методе applicationDidFinishLaunching:
— (void)applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:viewController.view];
// Override point for customization after app launch
[window makeKeyAndVisible];
}
В дальнейшем я буду полагаться на ваш опыт, полученный после прочтения моих постов и в ходе самостоятельного изучения материала, и буду опускать очевидные моменты в создании приложения, лишь оставляя указания по их реализации для экономии места и повышению уровня подачи и объема материала. Думаю, что детальное описание элементарных функций в статьях, объясняющих принципы построения сложных приложений, будет выглядеть неуместно.
Если вы сейчас запустите программу, то получите сообщение об ошибке, хотя мы не давали повода к их появлению. Дело в том, что у протоколов, которые реализует наш класс, есть ряд методов, обязательных к реализации. Исправлением этого недостатка мы сейчас и займемся, но прежде я расскажу о протоколах UITableViewDataSource и UITableViewDelegate.
Объект, реализующий первый протокол, предоставляет TableView информацию, необходимую для построения и модификации таблицы данных. По своей реализации этот объект является моделью из концепции Model-View-Controller. Он не имеет ни малейшего представления о том, как будет выглядеть и формироваться таблица — эту задачу выполняют методы UITableViewDelegate.
Минимальная часть информации, которую должен предоставить UITableViewDataSource — количество секций в таблице и число строк в каждой секции. Чтобы понять о чем идет речь нужно рассмотреть пример.
Представление объекта TableView ограничено одной колонкой таблицы по причине малого размера экрана устройства, но это не мешает представлять данные в удобном для пользователя виде. Чаще всего дробление на секции идет по алфавитному или группировке по функциональному признаку.
Для конкретики давайте составим список известных IT-компаний. Данные предлагаю представить в виде массива NSMutableArray и хранить его в переменной list, а инициализовать ее в tableViewController (кто у нас тут DataSource?) в методе awakeFromNib, который вызывается при загрузке файла интерфейса приложения, после инициализации всех элементов в него входящих.
-(void)awakeFromNib {
NSMutableArray *companies = [[NSMutableArray alloc] initWithObjects:@"Adobe", @"AMD",
@"AOL", @"Apple", @"IBM", @"Intel", @"Google",
@"Microsoft", @"Mozilla",@"Yahoo", @"Yandex", nil];
self.list = companies;
[companies release];
}
Если я пропустил вашу любимую компанию, то прошу прощения. Вы всегда можете скорректировать список согласно вашим предпочтениям :)
Метод numberOfSectionsInTableView протокола UITableViewDataSource возвращает количество секций в таблице, а tableView:numberOfRowsInSection: — количество строк в указанной секции. В нашей таблице количество секций равно единице, а количество строк в этой единственной секции равно количеству элементов в массиве:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [list count];
}
Следующими на очереди стоит метод tableView:cellForRowAtIndexPath: протокола UITableViewDelegate, который отвечает за визуальное представление информации в ячейке. Давайте разберем работу этого метода буквально построчно.
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CompanyCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:@"CompanyCell"] autorelease];
}
cell.text = (NSString *)[list objectAtIndex:indexPath.row];
return cell;
}
Метод возвращает для каждой ячейки объект класса UITableViewCell, о чем указано в объявлении метода. Очень интересный ход предприняли разработчики в Apple для минимизации потребления памяти при создании таблиц — повторное использование объектов UITableViewCell при формировании таблицы. Вы создаете определенного вида шаблоны UITableViewCell для каждого вида ячеек и повторно их используете, указывая имя шаблона при вызове метода dequeueReusableCellWithIdentifier. Ведь ячейки по сути отличаются друг от друга только содержимым. Зная, что блок ячеек будет одинаково выглядеть, за исключением содержимого, можно иметь только список этого содержимого и одну копию объекта UITableViewCell.
Если ячейка данного шаблона еще не была определена, формируем ее, используя конструктор объекта initWithFrame:reuseIdentifier:. В качестве размеров указываем константу CGRectZero, так как размер ячейки при отображении все равно будет подогнан автоматически.
Вторым параметром при вызове метода tableView:cellForRowAtIndexPath: идет так называемый IndexPath — объект, описывающий в общем виде путь к конкретному узлу в дереве подчиненных массивов. Звучит сложно, но на деле очень прост в понимании. Например, путь 1.4.3.2 можно представить в понятном графическом виде:
Для использования в UITableView класс NSIndexPath был расширен для упрощения его применения в таблицах (помните я говорил о категориях во Введении в Objective-C). Двух его свойств: row и section — достаточно для однозначного позиционирования в TableView. Так как в нашей таблице одна секция, используем indexPath.row для доступа к элементу массива.
И лишь одна деталь отделяет нас от успешного запуска программы — константа CGRectZero определена в фреймворке CoreGraphics, который не включен в наш проект. Для устранения этого недостатка в окне проекта в XCode нажмите правой кнопкой мыши на подраздел Frameworks, выберите Add -> Existing Frameworks… и добавьте CoreGraphics в ваш проект. На случай проблем привожу полный путь к папке с библиотеками iPhone (при установке XCode по умолчанию): /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.0.sdk/System/Library/Frameworks/.
Запустите программу и убедитесь, что вы только что успешно создали вашу первую таблицу TableView.
Все работает здорово, но вот незадача: при прикосновении к строке таблицы, чтобы пролистать ее содержимое, происходит выделение ячейки, чего мы совсем не желаем.
Это маленькое недоразумение исправляется реализацией метода tableView:willSelectRowAtIndexPath: протокола UITableViewDelegate.
-(NSIndexPath *)tableView:(UITableView *)tableView
willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
return nil;
}
По умолчанию метод возвращает адрес (NSIndexPath) ячейки, которая будет выделена при нажатии. Например, можно отправить адрес выделяемой ячейки, отличающейся от нажатой, переданной в параметре indexPath, или вообще ничего не выделять, как в нашем случае.
Скачать архив проекта simpleTableView (15Кб)Создание секций в таблицах
Чем больше данных в таблице, тем сложнее становится в ней ориентироваться. Первый шаг на пути к упрощению навигации лежит в использовании секций, главное определить критерий, по которому будет происходить группировка данных. Для нашего примера мы выберем вполне интуитивную группировку по алфавиту.
Для того, чтобы таблица могла разделять данные на секции, необходимо выполнить ряд условий:
- первым делом, как вы могли уже догадаться, мы должны возвратить количество секций в методе numberOfSectionsInTableView
- для каждой секции мы должны возвращать количество строк в ней через метод tableView:numberOfRowsInSection:
- для каждой секции нужно возвращать ее заголовок в методе tableView:titleForHeaderInSection:
Для выполнения этих условий нам придется изменить формат хранения наших данных. Каждая секция будет представлять собой отдельный элемент массива. Последние, в свою очередь, будут представлять собой словари, содержащие элемент с ключом “Title” (заголовок секции) и “Companies” (массив с названиями компаний).
-(void)awakeFromNib {
self.list = [[NSMutableArray alloc] init];
[list addObject:[NSDictionary dictionaryWithObjectsAndKeys:
@"A", @"Title",
[NSArray arrayWithObjects:@"Adobe", @"AMD", @"AOL", @"Apple", nil], @"Companies",
nil]];
[list addObject:[NSDictionary dictionaryWithObjectsAndKeys:
@"I", @"Title",
[NSArray arrayWithObjects:@"IBM", @"Intel", nil], @"Companies",
nil]];
[list addObject:[NSDictionary dictionaryWithObjectsAndKeys:
@"G", @"Title",
[NSArray arrayWithObjects:@"Google", nil], @"Companies",
nil]];
[list addObject:[NSDictionary dictionaryWithObjectsAndKeys:
@"M", @"Title",
[NSArray arrayWithObjects:@"Microsoft", @"Mozilla", nil], @"Companies",
nil]];
[list addObject:[NSDictionary dictionaryWithObjectsAndKeys:
@"Y", @"Title",
[NSArray arrayWithObjects:@"Yahoo", @"Yandex", nil], @"Companies",
nil]];
}
Методы, возвращающие количество строк и секций, подвергаются небольшому изменению:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [list count];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSDictionary *dict = [list objectAtIndex:section];
NSArray *companies = [dict objectForKey:@"Companies"];
return [companies count];
}
Также как и метод, возвращающий ячейки по индексу:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CompanyCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:@"CompanyCell"] autorelease];
}
NSDictionary *dict = [list objectAtIndex:indexPath.section];
NSArray *companies = [dict objectForKey:@"Companies"];
cell.text = (NSString *)[companies objectAtIndex:indexPath.row];
return cell;
}
И самое главное — метод, возвращающий заголовок для каждой секции:
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSDictionary *dict = [list objectAtIndex:section];
return [dict objectForKey:@"Title"];
}
Надеюсь, что код не вызывает вопросов, и я нигде не накосячил. Кстати, вы не забыли освободить переменную list в dealloc? ;) Запускайте программу и наслаждайтесь картиной.
Ради эксперимента, измените стиль таблицы в Interface Builder, в закладке Attributes инспектора свойств, с Indexed на Grouped, и вы получите внешний вид ничем не отличающийся от стиля приложения Настройки.
Скачать архив проекта tableViewWithSections (15Кб)Таблица с полосой навигации
Последним шагом для создания идеально удобного интерфейса будет добавление в наш TableView вертикальной индексной строки для быстрого перемещения между секциями. Наши действия снова укладываются в выполнение нескольких обязательных пунктов:
- метод sectionIndexTitlesForTableView должен возвращать массив с короткими названиями секций, в нашем случае мы будем возвращать одну букву на секцию
- метод tableView:sectionForSectionIndexTitle:atIndex: обязан возвратить номер секции по ее короткому названию
Для выполнения этих пунктов нам потребуется ввести дополнительную переменную indexList класса NSArray и инициализовать ее в методе awakeFromNib:
self.indexList = [[NSArray alloc] initWithObjects:@"A", @"I", @"G", @"M", @"Y", nil];
Также добавьте в наш код реализацию методов протокола UITableViewDataSource:
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return indexList;
}
-(NSInteger)tableView:(UITableView *)tableView
sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [indexList indexOfObject:title];
}
Учтите, что индекс секции в массиве indexList должен совпадать с индексом секции в массиве list.
Скачать архив проекта tableViewWithSectionIndex (16Кб)На этом примере я закончу свое повествование о применении UITableView. Любознательным читателям, желающим глубже ознакомиться с возможностями данного класса, предлагаю начать с самостоятельного разбора комплекта примеров приложений TableViewSuite (4-ый и 5-ый примеры), не забывая при этом заглядывать в справочную документацию.
Многоразовый редактор
Вам не показалось, что созданный нами в прошлом посте редактор обладает свойством одноразовости — попользовался и выкинул, ибо позволяет работать только над один документом одновременно? Как людям творческим нам требует куда более широкий фронт действий, в чем нам поможет применение TableView в нашем приложении. Вдогонку предлагаю пересмотреть способ хранения текста в пользу баз данных и использовать SQLite для этих целей. Правда, описание применения движка баз данных предлагаю оставить на следующий раз, а то я в очередной статье превышаю все разумные пределы на размеры стандартного блогового поста :)
Откройте проект txtEdit и удалите весь код, относящийся к сохранению и восстановлению данных сеанса — он нам больше не понадобится. И давайте опишем в общих словах как будет работать наше приложение после грядущих изменений.
Стартовый экран нашей программы будет содержать список документов, сформированный на основе UITableView. На строке навигации будет присутствовать кнопка добавления нового документа. Нажатие на эту кнопку или на документ из списка будет вызывать окно редактирования, которое будет выглядеть и работать также как и наше приложение в данный момент.
Что ж, приступим, и начнем с переработки интерфейса нашего будущего приложения. Для этого нам потребуется создать второй xib-файл, который будет содержать интерфейс окна редактирования, и который мы будем динамически загружать в приложение по мере необходимости.
Загрузите Interface Builder и создайте новый интерфейс на основе шаблона “View” для Cocoa Touch. Войдите в меню Files -> Read Class Files… и добавьте файл txtEditViewController.h в IB. Такой хитрый способ позволяет нам использовать уже готовые классы для привязки к объектам в интерфейсе. Теперь осталось только изменить имя класса объекта File’s Owner с NSObject на txtEditViewController, причем это можно сделать, выбрав имя класса в выпадающем списке инспектора свойств.
Осталось добавить к нашему интерфейсу недостающие элементы: UITextView и UINavigationItem.
И в последнюю очередь мы создаем недостающие связи:
- у File’s Owner переменные view, txtView и navigationItem связываются с объектами UIView, UITextView и UINavigationItem
- делегатом UITextView назначается File’s Owner
Ну и для полной красоты назначьте Title у объекта UINavigationItem как “Txt Edit”. На этом мы полностью завершили работу над созданием интерфейса окна редактирования. Сохраните созданный файл в каталоге проекта под названием “txtEdit.xib”.
Откройте MainWindow.xib, удалите объект View из нашего контроллера и добавьте на его место UITableView. Имя класса при этом смените с txtEditViewController на mainViewController.
Добавьте в правую часть строки навигации кнопку Bar Button Item и выберите для нее в свойствах Identifier под названием Add. Текст кнопки должен измениться на знак плюс.
А теперь мы создаем множество связей:
- delegate и dataSource для UITableView назначьте mainViewController
- в mainViewController создайте outlet переменную listView и свяжите ее с UITableView
- в mainViewController создайте action метод и закрепите его вызов за кнопкой на строке навигации
Сохраните все изменения и сгенерируйте файл нового класса mainViewController из Interface Builder. Назначьте родительский класс и все протоколы, а также сгенерируйте свойства самостоятельно.
Теперь мы готовы перейти к обсуждению работы всего этого безобразия и постепенному написанию кода. Начнем с обязательной программы — реализации протоколов для корректной работы UITableView. Так как описание работы и получение данных из SQLite мы благоразумно опустили в этой статье, оставим заглушки на методы UITableViewDataSource:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 0;
}
Помните, у нас до сих пор остался не задействован UINavigationController, а ведь именно в него мы должны передать объект txtEditViewController из файла нашего второго интерфейса. Как это сделать?
Во-первых, объявите новую переменную txtViewController класса txtEditViewController в заголовочном файле mainViewController.h, но не объявляйте для нее автоматическое создание свойства:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@class txtEditViewController;
@interface mainViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
IBOutlet UITableView *listView;
txtEditViewController *txtViewController;
}
@property (nonatomic, retain) UITableView *listView;
-(IBAction)addRecord;
@end
Мы напишем реализацию get-метода самостоятельно.
-(txtEditViewController *)txtViewController {
if (txtViewController == nil) {
txtViewController = [[txtEditViewController alloc] initWithNibName:@"txtEdit" bundle:nil];
}
return txtViewController;
}
В случае обращения к переменной txtViewController будет запрошен описанный выше get-метод. Если значение переменной еще не определено, класс будет инициализован чтением из файла интерфейса txtEdit.xib. Параметр bundle при этом указывает на место поиска файла интерфейса, в нашем случае — в главной папке приложения.
Не забудьте добавить заголовочный файл в mainViewController.m:
#import "txtEditViewController.h"
Таким образом, реализация нажатия на кнопку создания нового документа будет выглядеть так:
-(IBAction)addRecord {
[[self navigationController] pushViewController:self.txtViewController animated:YES];
}
Каждый ViewController содержит ссылку в переменной navigationController на опекающий его объект UINavigationController. А дальнейший код вполне очевиден: мы вызываем метод, помещающий txtViewController на вершину стека NavigationController и делающим активным его содержимое.
Допишем реализацию еще пары методов-заглушек протокола UITableViewDelegate:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return nil;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[[self navigationController] pushViewController:self.txtViewController animated:YES];
}
Вполне очевидно, что при нажатии на ячейку в таблице будет открыто окно редактирования с содержимым выбранного документа. Но пока данные не передаются у нас стоит только реализация открытия окна редактирования.
Сохраните все изменения, запустите и поиграйтесь с нашим приложением.
Все очень здорово работает, но при нажатии на кнопку создания нового документа кнопка возврата к предыдущему ViewController называется “Txt Edit”, а нам хотелось бы банального “Back”. У объекта UIViewController кроме предопределенных переменных leftBarButtonItem и rightBarButtonItem существует переменная backBarButtonItem, описывающая как будет выглядеть кнопка возврата к этому контроллеру. Напишем создание кнопки с правильной надписью в методе awakeFromNib.
-(void)awakeFromNib {
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] init];
backButton.title = @"Back";
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
}
Больше багов не нашли? А я со своим пытливым умом обнаружил почти сразу. Если ввести TextView в режим редактирования и нажать кнопку Back, то при возврате обратно к TextView пропадает клавиатура, но остается видимым курсор и кнопка Done.
Эта проблема решается дописыванием одной строчки в методе viewWillDisappear класса txtEditViewController. Мы просто вызовем процедуру окончания редактирования.
-(void)viewWillDisappear:(BOOL)animated {
[self doneAction:self];
}
Для корректного вызова метода не забудьте его определить в описании класса:
-(void)doneAction:(id)sender;
Больше багов не заметили? Поверните окно редактирования. Черный прямоугольник сбоку от TextView указывает нам на то, что UIView масштабируется неверно.
Для исправления бага откройте интерфейс txtEdit.xib в Interface Builder, выделите объект UIView и перейдите на закладку Size инспектора свойств. Измените параметры autoresizing, кликнув прямо по красным стрелочкам в окне, чтобы стало как показано ниже на скриншоте.
Вроде уже пора заканчивать, а все же нужно вернуться к тому, с чего начинали — к UITableViewController. Мы забыли про реализацию еще двух условий, о которых я не мог сказать ранее, пока мы не были знакомы со всеми действующими лицами нашей сегодняшней статьи:
- когда TableView появляется (первый раз или после возврата к окну его содержащему, как у нас при нажатии на Back) необходимо перезагрузить данные в нем при помощи встроенного метода reloadData
- когда TableView появляется после возврата к предыдущему состоянию, необходимо снять выделение ячейки, полученное после вызова tableView:didSelectRowAtIndePath:
Мои слова “когда TableView появляется” подсказывают нам место реализации кода — метод viewWillAppear в mainViewController.
-(void)viewWillAppear:(BOOL)animated {
NSIndexPath *selectedIndexPath = [listView indexPathForSelectedRow];
[listView deselectRowAtIndexPath:selectedIndexPath animated:NO];
[listView reloadData];
}
На этом действительно все. Оставляю вас разбираться в обилии информации, изложенной в статье, и до скорых встреч на страницах моего блога ;)
Скачать архив проекта txtEdit (18Кб)В качестве источников информации для статьи были использованы:
- документация XCode
- примеры приложений UICatalog, TableViewSuite и SQLiteBook
Комментарии
Сделайте пожалуйсто кат, чтобы не отдавать всю статью в рсс.
Поправил.
Мощно и круто, как всегда. Респект.
Вопрос, для get-метода txtViewController — мы не должны отпускать эту переменную в dealloc?
Спасибо.
Должны, я просто не стал об этом писать. Если взглянешь в исходный код, то в dealloc txtViewController освобождается.
Только у тебя как-то странно вопрос поставлен: “для get-метода txtViewController”. В самом get-методе мы не освобождаем переменную, т.к. она либо равна nil в первый вызов и мы в этом случае читаем ее содержимое из xib-файла, либо она уже занята и смысла ее переназначать нет.
Надеюсь, ответил на оба варианта вопроса :)
попробыывал переработать Ваш код, сделал вроде бы все правильно, но выкидывает аж 11 ероров!!!
” Linking/…/tableView.app/tableView (11 errors) “.objc_class_name_NSMutableArray”, referenced from: literal-pointer@OBJC@cls_refs@NSMutableArray in tableViewController.o … ”
в чем может быть ошибка. может неправильная связка объектов в IB???
Переработать? Это добавить функционалу в проект или попробовать согласно описанию, приведенному выше, сделать такой же? Если второе, то советую скачать архив проекта и построчно свериться. Буквально вчера связывались с одним читателем, у него тоже возникало несколько ошибок во время компиляции. Оказалось, что ошибся в написании названий методов протокола и компилятор подумал, что это обычный внутренний метод класса. Ну и похожие ошибки. Вобщем, советую свериться с исходниками.
все перепроверил, ничего не нашел, потом заменил свои .m и .h файлы проекта на Ваши — ошибки все те же
):
Вот в этом месте остановись. Какая версия SDK у тебя, какой Мак и версия Mac OS?
хакинтош леопарда iphone_sdk_final
Ага, вот в этом и проблема. Хакинтош с Mac OS 10.5.2 и старая версия (например, 4 или 7) бета iPhone SDK. Верно?
iphone os 2.0 mac os: x 10.5
Не, ты не понял. Открой яблоко->Об этом компьютере. В этом окне будет написана версия ОС вплоть до третьего знака, что имеет большое значение, т.к. iPhone SDK beta 9, с которой я работаю, встает лишь на версию 10.5.3, которую на хакинтоше, насколько я знаю, запустить еще не удалось.
Ты вот откуда ставил iPhone SDK? Ведь не качал небось с офф.сайта?
Mac OS X Version 10.5.4
Xcode version 3.1
Как тебе удалось поставить 10.5.4 на хакинтош? Ссылочку не кинешь на инструкцию?
Раз у тебя стоят требуемая версия инструментария и ОС, то попробуй просто открыть архив проекта и запустить приложение из него. Просто одной замены .m и .h файлов мало. Нужно еще заменить и включить в состав проекта xib-файлы, а также дополнительный фреймворк.
да ладно, я есче раньше помучался с примером кода скачанном с офф сайта, в итоге создал navigation-based app и все заработало, а потом нашол Вашу статейку. решил перенабрать код, разобраться как он работает…и вот такая трабла.
а по поводу инструментария, так я нашол ссылку, тока не помню где, мот но habrahabr или есче гдето. процесс установки интуитивно понятен, главное иметь совместимое железо( в оснавном intel) . бывает после или даже в процессе установки возникают разные баги, но в нете полно ссылок по их устранению. главное-правильно подготовить разделы.
10.5.4 без проблем ставится на хакинтош. Я ставил так: Сначала Kalyway 10.5.2 Затем патч Kalyway 10.5.3 После этого родной Apple-вский патч.
а я сразу поставил 10.5.4 -все оч просто
и есче! как можно сделать что-то а-ля файлового менеджера типа total comander. как пройтись по списку файлов находящихся на iphone??? первая часть примера по данной теме подходит в идеале, тока что бы вместо массива строк на экран выводились бы файлы. как я понимаю, надо создать массив NSString и инициализировать его списком файлов. как???
заранее благодарен Вам.
Как чувствовал, что пора форум заводить на сайте:) Я бы посоветовал глянуть в сторону классов NSFileHandle и NSFileManager. Если возникнут затруднения, то можно почитать статьи в этом блоге, начиная со “Знакомства с Objective-C”. Я стараюсь излагать материал последовательно, начиная практически с нулевого уровня подготовки, и пока ни у кого не возникало трудностей, значит стиль моего изложения выбран правильный.
ок!!! значит сёрфлю в правильном направлении. но ето наверно не последний вопрос по этой теме.
СПАСИБО!!!
Форма комментирования для «Вездесущий UITableView»
Не за что. Всегда готов ответить на дополнительные вопросы :)
какая тема следующего поста???
Следующий пост будет посвящен работе с базой данных, но его выход немного задержится.
SQLite , насколько я понимаю. тока на сколько заюержится??? в среднем тема выходит раз в три дня.
Я стараюсь придерживаться выхода раз в 5 дней, чаще редко получается, если только пост не касается программирования. К выходным будет готов.
я вывел на ээкран список файлов @”.” директории. как отреагировать на нажатие на какой-нибудь файл, то есть строке? в идеале я хочу войти в эту поддиректорию, точнее получить ее путь.
Об этом я напишу в следующем посте уже совсем скоро, хотя и в этой статье закинул удочку. Прочти ее все-таки внимательно :) Особенно про метод didSelectRowAtIndexPath
просёрфил эти классы-голова кругом!!! о4 много методов, о4 много возможностей…но я так и не понял,как прочитать и вывести на экран список файлов на iphone. и какой путь является корневым. и есче вопросик: как перейти в подкаталог. дайте please хотябы название методов, что бы было легче найти правильный путь.
я пробую перебрать через enumerator
ОЧЕНЬ БУДУ БЛАГОДАРЕН!!!
Послушай, ты пытаешься наскоком изучить язык, хотя толком не знаешь как он рабоает. Начни с самого простого: научись работе с Interface Builder, вникни в язык, в синтаксис, работу каждого метода. Не старайся сразу написать крупный проект, пиши простейшие вещи. Быстро только мухи плодятся.
И давай вынесем общение за пределы комментариев: пиши мне на почту или ICQ, указанные в контактах. Мы здесь треплемся не по теме статьи, что усложнит поиск ответов на вероятные вопросы будущих посетителей.
Ну да, он тебя заспамит вопросами, на которые мог бы ответить сам 5 минут погуглив, и более того, т.к. это будет твоя почта и ICQ, ему подобные на следующий день опять будут тратить твоё время.
Поставь уж лучше Ваниль тогда…
Да ответил на оба :)) Уфф, мне ещё учиться и учиться.
Получается, мы ещё базу данных не использовали в проекте? Только заглушку оставили?
Да. Создание и работа с базой описаны в следующем посте.
self.list = arr; error: request for member ‘list’ in something not a structure or union. а вообще откуда этот лист? чьё это поле? чё-т не понятно((
Эту переменную и свойства к ней надо объявить в заголовочном файле самостоятельно. Я об этом не писал, чтобы не опускаться до самых уж мелких подробностей, тем более исходники приложены к статье. Это можно сделать самому. Заодно можно закрепить свои знания :) Класс для list - NSMutableArray.
имхо, насчет мелких подробностей тут немного перебор… сначала идет переменная listView, потом откуда ни возьмись появляется list. и куда его девать? с чем связывать? куда девать listView? хз… сделай плз скидку для начинающих :)
listView никуда не пропадает. Это ссылка на UITableView и она используется по ходу работы приложения.
А вот с list я действительно немного перестарался, точнее недостарался :) Сейчас поправлю.
Я пока не супер специалист. У меня сложилось впечатление, что Интерфейс Буилдер как то недоработан. Получается часть связей мы пишем в ХСоде, часть ставим в Интерфейса, хотя с успехом эту связь могли бы 1-2 строками прописать прямо в коде… То есть Интерфейс Буилдер какой-то сырой что-ли. Получается вручную прописывать как то надёжнее…
У меня одного такое впечатление?
Interface Builder позволяет спрятать большую часть кода по созданию интерфейса и связей между элементами интерфейса. Если бы пришлось писать приложения без его участия, то ты бы сразу увидел как много кода (в зависимости от загруженности внешнего вида программы) приходится писать. В частности, программисты на toolchain так и страдают без IB.
Нельзя сказать, что IB сырой — ему лет 10-15 и за это время его успели отрихтовать до мелочи, чтобы с ним было удобно работать.
В идеале можно обойтись без написания кода руками, доверив эту функцию IB, предварительно создав интерфейс и выгрузить получившиеся связи в новый класс через Write Class File.
У меня вопросов нет, просто хотел сказать спасибо :) Очень понятно пишешь, давай есче ;)
Неточность — это метод задекларирован не в протоколе UITableViewDelegate, а в том же UITableViewDataSource.
Кстати, реализовывать numberOfSectionsInTableView не обязательно в случае, если он будет всегда возвращать 1 (это поведение принято по умолчанию).
А статья классная, все доступно и понятно, разложено по полочкам. Супер.
Странно что даже скачанный архив ничего не показывает( список компании) Ставил брейк на метод tableView:cellForRowAtIndexPath: управление туда ни разу не переходит Коментирую метод, компилятор ругается что отсутствует, но программа не вылетает
Разобрался в своем примере,код архива не смотрел Может это особенность моей реализации, но метод awakeTheNib (?) не вызывался и список не создавался Перенес в initWithNibFile - заработало
Огромное спасибо за ваши уроки. Очень помогли начать и, надеюсь, продолжить. Вот есть один вопрос. В приложении txtEdit после того, как в режиме работы с текстом я повернул экран, ввод символов остался ограничен той шириной, которая была до поворота в альбомную ориентацию. Может это особенности более нового SDK? У меня стоит Mac OS 10.6.2 и SDK для 3.1.2
Кстати говоря про SDK 3.1.2 … Не нашёл в Interface Builder-е редактор полей объекта … Это так? Или я “закривил” где-то?