Zombies & Me G-Force: The Game - Морские свинки шпионы
Июл 22

С помощью представления с подробным отображением (drill down table view) можно вывести на экран нижележащие уровни для нужных объектов. Для выбранной строки это будет новое табличное представление с относящимися к выбранному объекту данными. Процесс повторяется до тех пор, пока для элемента не останется категорий нижнего уровня. Это происходит при отображении детализированного представления, не являющегося “UITableView“. 

Создание проекта

В XCode создайте новый проект, выбрав опцию “Navigation-Based Application“. Для своего я выбрал имя “DrillDownApp“, которое и будет использоваться в уроке.

Один единственный “UITableView”

Вместо создания трех различных объектов “UITableView” для отображения трех различных уровней воспользуемся всего одним (и одним контроллером табличного представления). По духу такой подход близок веб-программированию, где для вывода списка продуктов достаточно одной страницы.

Структура потока данных

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

First Level Second Level Third Level Fourth Level Fifth Level Sixth Level
Item 1 Screen A Detail View N/A N/A N/A
Item 2 Screen B Screen C Detail View N/A N/A
Item 3 Screen D Screen E Screen F Detail View N/A
Item 4 Screen G Screen H Screen I Screen J Detail View

При запуске приложения видим следующие элементы: “Item 1“, “Item 2“, “Item 3” и”Item 4“. При выборе элемента “Item 1” табличное представление выводит экран “Screen A“, а при выборе “Screen A“ — загружает детализированное представление. То же происходит с “Item 2“, который загружает табличное представление с единственным значением “Screen B” -> “Screen C” -> Детализированное представление. Далее по тому же принципу для оставшихся значений первого табличного представления.

Создание источника данных

Исходными данными для этого урока выступит файл XML или plist. Выполнив “File -> New File” создайте новый файл в папке “Resource“, (в MAC OS X) выберите “Other -> Property List” и сохраните его, присвоив имя “Data.plist“. Для начала добавим значения к этому файлу списка свойств.

Выбрав элемент “Root“, создайте новый элемент, щелкнув на стрелке справа. 

Присвойте элементу имя “Rows“, а в качестве типа укажите “Array“. В этом массиве будут храниться все элементы начального уровня вместе с дочерними записями. Пользователь получает возможность, выбрав в табличном представлении элемент, загрузить другое табличное представление со всеми записями, относящимися к выбранному первым элементу. Для этого массиву потребуется словарь с полями как минимум двух типов — строка и массив. Строка предназначена для значения ячейки, а массив — для дочерних записей, где, в свою очередь, тоже будет содержаться n-ное количество словарей (n — число дочерних записей конкретного элемента). 

Создайте новый элемент “Rows” и в качестве типа укажите для него “Dictionary” (ключ для словаря менять не нужно). Выбрав словарь, создайте еще один элемент: тип данных — “string“, ключ — “Title“. Имя ключа должно быть единым во всем файле списка свойств — этот ключ будет хранить значение ячейки, отображаемой в “UITableViewCell”. В качестве значения указываем “Item 1“, поскольку это первый выводимый в табличном представлении элемент. Из приведенной выше таблицы нам известно, что выбор первого элемента перезагружает табличное представление и выводит “Screen A“. Под “Item 1” создаем еще одно поле, меняем его тип на “Array” и задаем ключ “Children“. Под элементом “Children” создаем еще один объект с типом “Dictionary“, в котором будет храниться исключительно строка “Screen A” с ключом “Title“.При выборе “Screen A” мы загружаем детализированное представление — на этом уровне нет необходимости добавлять дочерние записи. Внесите в список свойств оставшиеся элементы таблицы. В итоге список должен выглядеть следующим образом.

Наполнение источника данных

Сформировав источник данных, наполним его с помощью делегата приложения. Поскольку типом корневого элемента “data.plist” является “Dictionary“, создадим переменную типа “NSDictionary“, которая будет содержать все данные из файла списка свойств. Заголовочный файл делегата приложения меняется следующим образом:

[code='objc']
//DrillDownAppDelegate.h
@interface DrillDownAppAppDelegate : NSObject {

UIWindow *window;
UINavigationController *navigationController;

NSDictionary *data;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@property (nonatomic, retain) NSDictionary *data;

@end
[/code]

Переменная “data” содержит источник данных, который также синтезируется в файле реализации и освобождается в методе “dealloc” (код не указан).

Вот как вносятся данные в методе “applicationDidFinishLaunching“:

[code='objc']
//DrillDownAppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {

NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *DataPath = [Path stringByAppendingPathComponent:@"data.plist"];

NSDictionary *tempDict = [[NSDictionary alloc] initWithContentsOfFile:DataPath];
self.data = tempDict;
[tempDict release];

// Настройка и отображение окна
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
[/code]

Мы считываем файл “data.plist” и все данные в нем в словарь посредством сообщения “initWithContentsOfFile“, представленного в классе “NSDictionary“.

Вывод данных в табличном представлении

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

[code='objc']
//RootViewController.h
@interface RootViewController : UITableViewController {

NSArray *tableDataSource;
NSString *CurrentTitle;
NSInteger CurrentLevel;
}

@property (nonatomic, retain) NSArray *tableDataSource;
@property (nonatomic, retain) NSString *CurrentTitle;
@property (nonatomic, readwrite) NSInteger CurrentLevel;

@end
[/code]

Свойство “tableDataSource” содержит источник данных текущего уровня и имитирует файл списка свойств. Массив содержит n-ное количество словарей, где n—число словарей на данном уровне. В словаре будут храниться заголовок и массив с дочерними записями текущего элемента. Все свойства уже синтизированы, а “tableDataSource” и “CurrentTitle” освобождены в методе “dealloc“.

Вот как меняется метод “viewDidLoad“:

[code='objc']
//RootViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

if(CurrentLevel == 0) {

//Инициализация нашего источника табличных данных
NSArray *tempArray = [[NSArray alloc] init];
self.tableDataSource = tempArray;
[tempArray release];

DrillDownAppAppDelegate *AppDelegate = (DrillDownAppAppDelegate *)[[UIApplication sharedApplication] delegate];
self.tableDataSource = [AppDelegate.data objectForKey:@"Rows"];

self.navigationItem.title = @”Root”;
}
else
self.navigationItem.title = CurrentTitle;
}
[/code]

При значении “CurrentLevel“, равном нулю (например, при запуске приложения), массив инициализируется и заполняется из делегата приложения. Здесь получаем массив с установленным на “Rows” ключом, который будет содержать данные начального уровня и дочерние записи для каждого элемента. Заголовок элемента навигации в данном случае устанавливается на “Root“. Если нет, для заголовка задается переменная “CurrentTitle“, а “tableDataSource” остается без изменений. Два этих свойства указываются в методе “tableView:didSelectRowAtIndexPath“, который будет работать с ними позже. 

Отображение данных

Метод “tableView:numberOfRowsInSection” возвращает подсчет массива:

[code='objc']
//RootViewController.m
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.tableDataSource count];
}
[/code]

Измененный метод “tableView:cellForRowAtIndexPath” выглядит следующим образом:

[code='objc']
//RootViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @”Cell”;

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

// Настройка ячейки…
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];
cell.text = [dictionary objectForKey:@"Title"];

return cell;
}
[/code]

Из созданного нами источника данных мы знаем, что в каждом массиве есть минимум по одному элементу типа “Dictionary” и с ключом “Title“, являющимся выводимой в ячейке табличного представления строкой. После создания ячейки “UITableView” получаем из массива словарь и значение для ключа “Title“, настроенного на текстовое свойство ячейки.

Выбор строки

При выборе строки вызывается метод “tableView:didSelectRowAtIndexPath“, выглядящий следующим образом:

[code='objc']
//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Получаем словарь выбранного источника данных.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

//Получаем дочерние записи текущего элемента.
NSArray *Children = [dictionary objectForKey:@"Children"];

if([Children count] == 0) {

}
else {

//Готовим для “tableview”.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:@”RootViewController” bundle:[NSBundle mainBundle]];

//Прирост текущего представления
rvController.CurrentLevel += 1;

//Настройка заголовка;
rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

//Помещаем новое табличное представление в стэк
[self.navigationController pushViewController:rvController animated:YES];

rvController.tableDataSource = Children;

[rvController release];
}
}
[/code]

Сначала получаем словарь для выбранной строки, а потом помещаем в массив соответствующие дочерние записи. Если таковых для текущего уровня нет, на этом пока все. Если есть, загружаем то же самое табличное представление с другим источником данных. Инициализируем прежний контроллер “RootViewcontroller“, приращиваем значение “CurrentLevel“, чтобы оно отличалось от нуля, настраиваем текст в выбранной ячейке на “CurrentTitle“, помещаем контроллер представления в верхнюю часть стека, задаем для нового источника данных свойство “tableDataSource“.

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

Загрузка детализированного представления

Создайте новое представление (в редакторе интерфейсов IB) и присвойте ему имя “DetailView“. Не забудьте, что представления хранятся в папке “Resources“. В Xcode создайте новый “UIViewController” в папке “Classes” и присвойте ему имя “DetailViewController“. Для относящегося к “DetailView” объекта “File’s owner” укажите класс “DetailViewController” и свяжите переменную outlet с объектом в представлении (в nib-файле). 

В относящемся к “DetailViewController” методе “viewDidLoad” настройте заголовок представления на “Detail View“. Вот как при этом будет выглядеть код:

[code='objc']
//DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

self.navigationItem.title = @”Detail View”;
}
[/code]

Когда у элемента не останется дочерних записей, загрузим детализированное представление. Вот как при этом изменится метод “tableView:didSelectRowAtIndexPath“:

[code='objc']
//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Получаем словарь выбранного источника данных
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

//Получаем дочерние записи текущего элемента.
NSArray *Children = [dictionary objectForKey:@"Children"];

if([Children count] == 0) {

DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:@”DetailView” bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
}
else {

//Готовим для “tableview”.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:@”RootViewController” bundle:[NSBundle mainBundle]];

//Прирост текущего представления
rvController.CurrentLevel += 1;

//Настройка заголовка;
rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

//Помещаем новое табличное представление в стек
[self.navigationController pushViewController:rvController animated:YES];

rvController.tableDataSource = Children;

[rvController release];
}
}
[/code]

Заключение

С помощью предложенной методики к элементу добавляется любое количество дочерних записей без редактирования кода. Для занесения данных на шестой уровень не потребуется создавать еще одно табличное представление — достаточно внести в источник данных новый элемент, а остальные действия выполнит код. Здесь используется n-ное количество объектов табличного представления, включая пример от Apple. Удачи в программировании!

Исходный код скачать можно [здесь]

Текст оригинальной статьи на английском языке [здесь]

Уважаемые читатели, данный материал был переведен и подготовлен к публикации проектом LookApp.ru, при публикации на другом сайте ссылка на LookApp.ru обязательна.

1 звезда2 звезд3 звезд4 звезд5 звезд (2 голосов, средний: 5.00 из 5)
Загрузка ... Загрузка ...


Оставьте комментарий