Статьи

iOS лаконично — мультисценовые приложения

В предыдущей главе был представлен основной рабочий процесс разработки приложений для iOS, но мы работали в рамках приложения с одним представлением. Однако большинству реальных приложений требуется несколько сцен для иерархического представления данных. В то время как существует много типов организационных шаблонов, используемых для управления многосценовыми приложениями, в этой главе рассматривается один из наиболее распространенных шаблонов: приложение master-detail.

Минимальное приложение master-detail состоит из «главной» сцены, которая представляет список элементов данных на выбор, и «детализации» сцены, которая отображает детали элемента, когда пользователь выбирает его из главной сцены. Откройте приложение «Почта» на своем iPhone, и вы найдете хороший пример приложения с подробной информацией. В папке «Входящие» перечислены ваши сообщения, что делает их главной сценой, и когда вы выбираете одно из них, сцена подробностей используется для отображения содержимого сообщения, отправителя, любых вложений и т. Д.

В большинстве случаев мультисценовые приложения используют тот же рабочий процесс, который обсуждался в предыдущей главе. Мы по-прежнему будем создавать макеты, добавляя компоненты пользовательского интерфейса через Interface Builder и подключая их к нашему коду с помощью действий, выходов и делегатов. Однако наличие нескольких сцен означает, что у нас будет несколько контроллеров представления, поэтому нам необходимо использовать новый класс UINavigationController для организации их взаимодействия. Мы также узнаем, как настраивать переходы сцен с использованием сегментов.


Примером проекта для этой главы будет простой список контактов, который позволяет пользователям управлять списком своих друзей и соответствующей контактной информацией. Чтобы создать пример проекта, выберите «Файл»> «Создать»> «Проект» и выберите приложение «Master-Detail». Это даст нам возможность изучить новые навигационные компоненты и организационные структуры, а также способы обработки переходов из одной сцены в другую.

tutorial_image
Рисунок 31: Создание шаблона приложения Master-Detail

Используйте FriendList для поля «Название продукта», все, что вам нравится для названия организации, и edu.self для идентификатора компании. Как и в предыдущем приложении, убедитесь, что выбран iPhone «Устройство» и «Использовать раскадровки» и «Использовать автоматический подсчет ссылок»:

tutorial_image
Рисунок 32: Настройка проекта

Вы можете сохранить проект где угодно.


Мы будем опираться на существующий код шаблона, поэтому давайте кратко рассмотрим приложение по умолчанию. Нажмите кнопку «Выполнить» в верхнем левом углу Xcode или нажмите Cmd + R, чтобы скомпилировать приложение и запустить его в iOS Simulator. Вы должны увидеть пустой список, озаглавленный Master с кнопкой Edit и кнопкой Add (знак плюс) на панели навигации. Нажатие на кнопку «Добавить» добавит новый элемент в список, а выбор этого элемента приведет к переходу в детальную сцену. Обе сцены показаны на следующем рисунке.

tutorial_imagetutorial_image
Рисунок 33. Основные и подробные сцены шаблона по умолчанию

Элементами данных по умолчанию, используемыми шаблоном, являются даты, но мы собираемся изменить основную сцену, чтобы отобразить список имен и подробную сцену, чтобы отобразить их контактную информацию.

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

Как и в предыдущей главе, класс AppDelegate позволяет вам реагировать на важные события в жизненном цикле приложения. Нам не нужно настраивать поведение при запуске для нашего списка друзей, поэтому мы вообще не будем редактировать этот класс.

Вместо одного ViewController этот шаблон имеет два класса контроллера представления: MasterViewController и DetailViewController. Они управляют главной сценой и детальной сценой, а их методы viewDidLoad служат точкой входа в их соответствующие сцены. Метод viewDidLoad в MasterViewController должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
— (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.leftBarButtonItem = self.editButtonItem;
 
    UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                             target:self
                             action:@selector(insertNewObject:)];
    self.navigationItem.rightBarButtonItem = addButton;
}

Это создает кнопки «Редактировать» и «Добавить», которые вы видите в верхней части главной сцены, и устанавливает метод insertNewObject: в качестве действия для последней. Метод insertNewObject: добавляет новый экземпляр NSDate к закрытой переменной _objects, которая является изменяемым массивом, содержащим основной список элементов данных, и все методы после метки #pragma — Table View определяют, как этот список отображается в сцена. Метод prepareForSegue: sender: вызывается перед переходом на сцену детали, и именно там необходимая информация переносится из главной сцены в сцену детали.

Класс DetailViewController немного проще. Он просто объявляет свойство detailItem для хранения выбранного элемента и отображает его через выход detailDescriptionLabel. Мы собираемся изменить эту реализацию по умолчанию для отображения контактной информации человека.

Раскадровка, пожалуй, самое радикальное изменение по сравнению с предыдущим примером. Если вы откроете MainStoryboard.storyboard, вы должны увидеть следующее:

tutorial_image
Рисунок 34: раскадровка по умолчанию для шаблона

Вместо одного контроллера представления, Interface Builder теперь управляет тремя контроллерами. Это может показаться странным, учитывая, что наше приложение имеет только две сцены, но и MasterViewController, и DetailViewController встроены в экземпляр UINavigationController. Именно благодаря этому контроллеру навигации мы видим панель навигации в верхней части приложения, и именно это позволяет нам перемещаться вперед и назад между главной и подробной сценами. Мы поговорим больше о настройке контроллеров навигации в этой главе.

Этот шаблон должен также пояснить, почему файл MainStoryboard.storyboard называется «раскадровкой» — он визуализирует не только сами сцены, но и поток между этими сценами. Как и в предыдущей главе, стрелка, указывающая на контроллер навигации, показывает, что это корневой контроллер. Но мы также видим другую стрелку от контроллера навигации к MasterViewController и от MasterViewController к DetailViewController. Эти стрелки определяют отношения и переходы между всеми контроллерами представления в приложении.


В отличие от предыдущей главы, это приложение будет использовать специальный класс для представления данных модели. Мы будем использовать класс Person для хранения контактной информации каждого друга. В Xcode создайте новый файл, выберите класс Objective-C и введите Person для поля Class, например так:

tutorial_image
Рисунок 35: Создание класса Person

Далее нам нужно объявить несколько свойств для записи имени, организации и номера телефона каждого контакта. Откройте Person.h и измените его следующим образом:

1
#import <Foundation/Foundation.h> @interface Person : NSObject @property (copy, nonatomic) NSString *firstName;

Конечно, нам также необходимо синтезировать эти свойства в Person.m.

01
02
03
04
05
06
07
08
09
10
#import «Person.h»
 
@implementation Person
 
@synthesize firstName = _firstName;
@synthesize lastName = _lastName;
@synthesize organization = _organization;
@synthesize phoneNumber = _phoneNumber;
 
@end

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


Далее мы настроим основную сцену для отображения списка объектов Person. Определение поведения сцены требует тщательного взаимодействия между исходным кодом основного контроллера представления и визуальным представлением в Интерфейсном Разработчике. Прежде чем приступить к программированию, давайте подробнее рассмотрим основную сцену шаблона в раскадровке.

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

tutorial_image
Рисунок 36: Отношение между корневым контроллером навигации и главным контроллером представления

Шаблон настроил эти отношения для нас, но важно иметь возможность настроить его самостоятельно. Итак, удалите навигационный контроллер, выбрав его и нажав «Удалить». Чтобы воссоздать связь, выберите желтый значок View Controller в главном контроллере представления, а затем перейдите к Editor в строке меню Xcode и выберите «Embed In»> «Navigation Controller». Должен появиться новый навигационный контроллер, и вы должны вернуться туда, откуда начали.

Важно понимать, что стрелка взаимосвязи не означает переход между контроллером навигации и главным контроллером. Скорее, встраивание нашей главной сцены в контроллер навигации таким образом создает иерархию контроллера представления. Это говорит о том, что главная сцена принадлежит навигационному контроллеру. Это позволяет переключаться между сценами, используя встроенные переходы контроллера навигации и кнопки навигации. Например, кнопка «Мастер», отображаемая в верхней части подробной сцены, автоматически добавляется навигационным контроллером:

tutorial_image
Рисунок 37: Встроенная навигационная кнопка, предоставляемая навигационным контроллером

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

Segue представляет переход от одной сцены к другой. Как и отношения, он отображается как стрелка из исходной сцены в сцену назначения, но использует другой значок. Обратите внимание, что при щелчке по значку перехода выделяется только одна ячейка таблицы. Это говорит нам о том, что переход прикреплен к отдельным ячейкам таблицы, а не ко всей главной сцене.

tutorial_image
Рисунок 38: Переход от главной сцены к сцене детализации

Опять же, наш шаблон создал этот переход для нас, но важно иметь возможность создать его с нуля. Итак, выберите значок перехода и нажмите «Удалить», чтобы удалить его из раскадровки. Чтобы воссоздать его, перетащите его из ячейки таблицы на сцену детализации.

tutorial_image
Рисунок 39: перетаскивание элемента управления из ячейки таблицы мастера в детальную сцену

Откроется меню, предлагающее выбрать тип действия Sege / Accessory Action. Мы хотим, чтобы наш переход происходил, когда пользователь выбирает ячейку таблицы, поэтому выберите push в группе Sege Selection.

tutorial_image
Рисунок 40: Выбор типа segue для создания

Родительский UINavigationController управляет своими сценами через стек навигации, а его pushViewController: animated: и popViewControllerAnimated: методы позволяют добавлять или удалять экземпляры контроллера представления из стека. Например, помещая объект контроллера подробного представления в стек навигации, вы переходите к подробной сцене, а нажатие кнопки «Мастер» на панели навигации подробной сцены извлекает его из стека навигации. Выбор push из меню на Рисунке 40 подсказывает последовательность действий для вызова pushViewController: animated: метод для перехода от главной сцены к сцене детализации.

В дополнение к типу, каждый segue также должен иметь уникальный идентификатор, чтобы к нему можно было получить доступ из вашего исходного кода. Вы можете редактировать идентификатор segue, выбрав значок segue и открыв панель инспектора атрибутов. Наш segue должен иметь идентификатор showDetail, и вы также должны увидеть тип push push в поле Style:

tutorial_image
Рисунок 41: Инспектор Атрибутов для сеанса master-detail

Другой вариант стиля — это Modal, который представляет другую сцену поверх существующей сцены, полностью независимую от родительского контроллера навигации. Вы должны оставить стиль этого перехода как Push (мы создадим модальный переход к концу этой главы).

Одним из основных отличий нашей главной сцены от ViewController из предыдущей главы является тот факт, что она наследуется от UITableViewController вместо UIViewController. Контроллер табличного представления управляет экземпляром UITableView. Табличные представления состоят из одного столбца строк, возможно, сгруппированных в разделы. Это делает их хорошо подходящими для представления списков данных.

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

tutorial_image
Рисунок 42: Выбор экземпляра UITableView из схемы документа

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

tutorial_image
Рисунок 43. Инспектор атрибутов для табличного представления главной сцены

Если вы установите в поле «Содержимое» значение «Динамические прототипы», вы можете создавать новые ячейки, дублируя прототипную ячейку, разработанную в Интерфейсном Разработчике. Статические ячейки, с другой стороны, не могут быть дублированы, что приводит к статической таблице. Это означает, что вы должны использовать динамические прототипы, когда вы хотите вставить или удалить строки на лету, и использовать статические ячейки, когда ваша таблица всегда показывает одинаковое количество информации. Держите стол главной сцены динамичным. Мы будем использовать статическую таблицу для детализации сцены.

Когда вы используете ячейки прототипа, вам нужно дать каждому прототипу уникальный идентификатор, чтобы к нему можно было получить доступ из вашего исходного кода (точно так же, как и идентификатор сегмента). Чтобы изменить идентификатор ячейки прототипа, выберите ячейку в редакторе сцены или в конструкторе интерфейса и откройте инспектор атрибутов. Идентификатор для этого конкретного прототипа можно установить в поле «Идентификатор», как показано на следующем рисунке. Поскольку в этом приложении у нас будет только одна прототипная ячейка, вы можете оставить значение ячейки по умолчанию, но для реальных приложений вы должны дать каждому прототипу описательный идентификатор.

tutorial_image
Рисунок 44. Инспектор атрибутов для ячейки таблицы прототипа

Стоит также взглянуть на инспектор соединений для UITableView (не на ячейку прототипа). Вы должны увидеть источник данных и выход делегата, и оба они должны указать класс MasterViewController для своего места назначения.

tutorial_image
Рисунок 45: Выходные соединения для представления таблицы главной сцены

Источник данных табличного представления — это особый тип делегата, который предоставляет информацию для каждой строки в таблице. Помимо необработанных данных, делегат табличного представления необходим для определения поведения таблицы и внешнего вида каждой строки. Как и в случае с делегатами приложения и текстового поля, они реализуются через протоколы, называемые UITableViewDataSource и UITableViewDelegate, соответственно.

В этом случае класс MasterViewController действует как источник данных и делегат, поэтому в шаблоне master-detail включены такие методы, как tableView: cellForRowAtIndexPath: и tableView: canEditRowAtIndexPath: in MasterViewController.m. В следующем разделе мы изменим эти методы, чтобы изменить внешний вид списка друзей.

Теперь, когда мы лучше разбираемся в том, что происходит в раскадровке, мы готовы приступить к настройке нашего класса MasterViewController. Прямо сейчас главная сцена отображает список объектов NSDate, но мы хотим изменить их на объекты Person. Конечно, это означает, что нам потребуется доступ к классу Person, поэтому импортируйте заголовок в MasterViewController.m:

1
#import «Person.h»

Помните, что viewDidLoad: метод сообщает кнопке Add главной сцены, чтобы она вызывала метод insertNewObject: всякий раз, когда пользователь нажимает на него. Вместо добавления объекта даты в массив _objects нам нужен insertNewObject: для добавления объекта Person. Измените это на следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
— (void)insertNewObject:(id)sender {
    if (!_objects) {
        _objects = [[NSMutableArray alloc] init];
    }
    Person *friend = [[Person alloc] init];
    friend.firstName = @»<First Name>»;
    friend.lastName = @»<Last Name>»;
    friend.organization = @»<Organization>»;
    friend.phoneNumber = @»<Phone Number>»;
    [_objects insertObject:friend atIndex:0];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    [self.tableView insertRowsAtIndexPaths:@[indexPath]
                    withRowAnimation:UITableViewRowAnimationAutomatic];
}

Это создает новый объект Person и заполняет его некоторыми фиктивными значениями, а затем добавляет его в начало массива _objects с помощью insertObject: atIndex :. Экземпляр NSIndexPath представляет собой простой объект данных, представляющий индекс конкретной ячейки, а insertRowsAtIndexPaths: withRowAnimation: добавляет новую ячейку в указанном месте.
Обратите внимание, что этот последний метод фактически не создает новую ячейку — он просто добавляет элемент в массив _objects и сообщает таблице, что в нем должна быть еще одна строка. Это побуждает таблицу создать новую ячейку, которая подготавливается методом tableView: cellForRowAtIndexPath: делегат источника данных. Это должно выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
— (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView
                             dequeueReusableCellWithIdentifier:@»Cell»
                             forIndexPath:indexPath];
    Person *friend = _objects[indexPath.row];
    cell.textLabel.text = [NSString stringWithFormat:@»%@ %@»,
                           friend.firstName, friend.lastName];
    return cell;
}

Этот метод вызывается каждый раз, когда таблице необходимо визуализировать данную ячейку, и он должен возвращать объект UITableViewCell, представляющий соответствующую строку. Сначала мы выбираем ячейку прототипа, используя идентификатор, определенный в раскадровке, а затем мы используем экземпляр NSIndexPath, чтобы найти связанный объект Person. Наконец, мы отображаем имя человека через свойство textLabel ячейки.

Теперь вы должны иметь возможность добавлять, просматривать и удалять объекты Person из главной сцены:

tutorial_image
Рисунок 46: Добавление объекта Person в главную сцену

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

Помните, что UINavigationController и push-segue обрабатывают переход для нас, но он дает нам возможность отправлять данные из контроллера представления источника в контроллер представления назначения, вызывая метод prepareForSegue: sender: непосредственно перед тем, как он переключается в представление подробностей. Измените prepareForSegue: sender: в MasterViewController.m на следующее (единственное реальное изменение — использование объекта Person вместо экземпляра NSDate):

1
2
3
4
5
6
7
— (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([[segue identifier] isEqualToString:@»showDetail»]) {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        Person *friend = _objects[indexPath.row];
        [[segue destinationViewController] setDetailItem:friend];
    }
}

Этот метод — то, как мы передаем данные между главной сценой и сценой детализации. Он вызывается для каждого перехода, связанного с конкретным контроллером, поэтому наш первый шаг — проверка идентификатора перехода, который был определен в Интерфейсном Разработчике. Затем мы используем метод indexPathForSelectedRow, чтобы получить индекс выбранной строки (не так уж и хороши соглашения об именах Objective-C), и мы используем этот индекс, чтобы найти соответствующий элемент данных из массива _objects. Наконец, мы передаем этот объект в сцену детали, устанавливая его свойство detailItem.

tutorial_image
Рисунок 47: Выбор объекта Person из главной сцены

Теперь, когда вы выбираете элемент из основного списка, вы должны увидеть объект Person вместо экземпляра NSDate. Подробная сцена по умолчанию использует метод description для преобразования объекта в строку, поэтому мы видим адрес памяти на рисунке 47 вместо какой-либо значимой информации. Мы изменим это в следующем разделе.

Подводя итог нашей главной сцене: у нас есть соединение отношений, которое встраивает его в экземпляр UINavigationController, последовательность, определяющую переход к сцене детализации, ячейку прототипа, которую мы используем в качестве шаблона для новых строк таблицы, кнопку «Добавить», которая добавляет фиктивные экземпляры. к основному списку элементов данных и метод prepareForSegue: sender:, который передает выбранный элемент в детальную сцену.


Далее нам нужно настроить детальную сцену для отображения выбранного друга. Один объект Person всегда имеет одинаковый объем информации (имя, организация и номер телефона), поэтому мы будем использовать три статических ячейки для форматирования вывода вместо динамических прототипов. Как и в главной сцене, мы собираемся сначала сконфигурировать Interface Builder, а затем кодировать функциональность после того, как у нас есть компоненты UI.

Шаблон master-detail использует простой ViewController для сцены детализации, поэтому наша первая задача — заменить его на UITableViewController. В Интерфейсном Разработчике выберите сцену деталей и нажмите Удалить, чтобы удалить ее из раскадровки. Затем перетащите объект Table View Controller из библиотеки объектов в редактор сцен.

tutorial_image
Рисунок 48. Контроллер табличного представления в библиотеке объектов

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

tutorial_image
Рисунок 49: Воссоздание push-перехода от главной сцены до сцены детализации

Это объединяет контроллер табличного представления с иерархией навигации, и Interface Builder отражает это, добавляя панель навигации в верхнюю часть сцены детализации. Однако эта панель навигации теперь пуста. Давайте исправим это, дважды щелкнув по центру пустой панели навигации и введя Detail в качестве заголовка сцены, вот так:

tutorial_image
Рисунок 50: Определение заголовка сцены детали

Нам также нужно подключить новую сцену к нашему классу DetailViewController. Перед изменением класса в конструкторе интерфейсов нам нужно заставить DetailViewController наследоваться от UITableViewController. Измените объявление интерфейса в DetailViewController.h на следующее:

1
@interface DetailViewController : UITableViewController

Затем снова откройте раскадровку, выберите желтый значок в доке контроллера табличного представления, откройте инспектор компонентов и измените класс на DetailViewController.

tutorial_image
Рисунок 51: Установка нового класса контроллера табличного представления

Теперь мы вернулись к тому, с чего начали, но вместо обычного контроллера просмотра у нас есть контроллер табличного представления. Помните, что мы собираемся использовать статическую таблицу для размещения информации о выбранном объекте Person. Итак, выберите детальный вид сцены детали из контура документа.

tutorial_image
Рисунок 52: Выбор табличного представления сцены детали

Затем измените поле «Содержимое» на «Статические ячейки» в инспекторе Атрибутов. Вы также можете изменить Разделитель на Нет и Выбор на Нет выбора. Это удаляет линию между ячейками и не позволяет пользователям выбирать их.

tutorial_image
Рисунок 53: Изменение содержимого табличного представления с динамических прототипов на статические ячейки

Теперь вы должны увидеть три пустые ячейки в детализации сцены. Выделите все из них, удерживая Shift и щелкнув по ним, а затем измените их стиль на Левая деталь в инспекторе атрибутов. Это добавляет заголовок и метку сведений к каждой ячейке. Измените заголовки на «Имя», «Телефон» и «Организация», чтобы сцена детализации выглядела следующим образом:

tutorial_image
Рисунок 54: Настройка заголовков статических ячеек

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

Это все, что мы можем сделать в Интерфейсном Разработчике на данный момент. Давайте добавим несколько свойств в DetailViewController, чтобы мы могли получить доступ к только что добавленным меткам сведений. Измените DetailViewController.h на следующее:

01
02
03
04
05
06
07
08
09
10
#import <UIKit/UIKit.h>
 
@interface DetailViewController : UITableViewController
 
@property (strong, nonatomic) id detailItem;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *organizationLabel;
@property (weak, nonatomic) IBOutlet UILabel *phoneNumberLabel;
 
@end

Вспомните из предыдущей главы, что модификатор IBOutlet делает эти свойства доступными для Interface Builder. Далее, синтезируйте эти свойства в DetailViewController.m:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
#import «DetailViewController.h»
#import «Person.h»
 
@implementation DetailViewController
 
@synthesize detailItem = _detailItem;
@synthesize nameLabel = _nameLabel;
@synthesize organizationLabel = _organizationLabel;
@synthesize phoneNumberLabel = _phoneNumberLabel;
Then, change the configureView method to set the value of the detail labels based on the Person object passed in from the master scene:
— (void)configureView {
    if (self.detailItem &&
        [self.detailItem isKindOfClass:[Person class]]) {
        NSString *name = [NSString stringWithFormat:@»%@ %@»,
                          [self.detailItem firstName],
                          [self.detailItem lastName]];
        self.nameLabel.text = name;
        self.organizationLabel.text = [self.detailItem organization];
        self.phoneNumberLabel.text = [self.detailItem phoneNumber];
    }
}

Также обратите внимание, что мы используем isKindOfClass: метод, чтобы гарантировать, что детальный элемент фактически является объектом Person. Это лучший шаг при использовании динамически типизированных переменных, таких как detailItem.

Наш последний шаг для детальной сцены — это подключение полей nameLabel, organizationLabel и phoneNumberLabel к соответствующим компонентам UILabel в раскадровке. Это можно сделать, выбрав желтый значок в доке сцены детали и перетащив из кругов в инспекторе соединений на компоненты меток в редакторе сцены. Обязательно перетащите каждую розетку на соответствующие ярлыки.

tutorial_image
Рисунок 55: Подключение компонентов метки к контроллеру подробного представления

Когда вы компилируете приложение, вы сможете выбрать элементы из основного списка и просмотреть их детали в детальной сцене. Обратите внимание, что мы можем отображать только детали; мы не можем их редактировать.

tutorial_image
Рисунок 56: завершенная сцена детализации

Подводя итог изменениям в нашей сцене детализации: мы заменили контроллер по умолчанию на компонент контроллера табличного представления, изменили DetailViewController для наследования от UITableViewController, заново создали переход от главной сцены к сцене детализации и объявили несколько свойств, которые служили в качестве выходов. из DetailViewController в экземпляры UILabel. Целью всего этого было отображение свойств экземпляра Person, который был выбран в главной сцене.


Наша последняя работа в этой главе — добавить еще одну сцену, которая позволит нам редактировать выбранный элемент. Вместо толчка, мы собираемся реализовать эту новую сцену, используя модальный переход. Модальный переход представляет конечную сцену «поверх» существующей сцены, во многом как всплывающее окно на настольном компьютере. Это не влияет на иерархию навигации, поэтому вместо родительского UINavigationController, который берет на себя ответственность за навигацию между сценами, модально представляемая сцена закрывается при необходимости.

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

Прежде чем мы сможем создать модальный переход, нам нужно отредактировать сцену для работы. Эта сцена будет работать почти так же, как сцена детализации, за исключением того, что она будет иметь компоненты UITextField вместо UILabels, чтобы пользователь мог редактировать каждое свойство. Сначала создайте новый класс с именем EditViewController и используйте UITableViewController для суперкласса:

tutorial_image
Рисунок 57: Создание класса для Edit View Controller

Затем откройте Interface Builder и перетащите другой контроллер табличного представления из библиотеки объектов в редактор сцен. Расположите его над сценой детали, например, так:

tutorial_image
Рисунок 58: Добавление контроллера табличного представления в раскадровку

Этот новый контроллер должен быть подключен к классу EditViewController, который мы только что создали, поэтому выберите его в редакторе интерфейса, откройте инспектор удостоверений и измените поле класса на EditViewController.

tutorial_image
Рисунок 59: Определение класса нового контроллера табличного представления

Наша сцена редактирования будет использовать панель навигации для отображения кнопок Отмена и Сохранить. Мы могли бы встроить его в корневой UINavigationController, но помните, что мы хотим представить его модально, а не помещая его в существующий стек контроллера представления. Чтобы предоставить ему собственную панель навигации, все, что нам нужно сделать, это встроить ее в свой собственный контроллер навигации. Выберите Edit View Controller в Интерфейсном Разработчике и выберите Editor> Embed In> Navigation Controller из меню Xcode.

tutorial_image
Рисунок 60: Встраивание сцены редактирования в новый контроллер навигации

В то время как push-сегменты позволяют содержащему навигационному контроллеру добавлять кнопки навигации для вас, нам нужно добавить наши собственные кнопки для модального перехода. UIKit Framework использует специальную категорию элементов управления для использования в панелях навигации. Мы ищем элемент панели кнопок, который вы можете найти в разделе Windows & Bars библиотеки объектов.

tutorial_image
Рисунок 61: Элемент панели кнопок в библиотеке объектов

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

tutorial_image
Рисунок 62: Добавление кнопки редактирования в панель навигации подробной сцены

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

tutorial_image
Рисунок 63: Изменение кнопки панели на кнопку редактирования

Эти предопределенные типы позволяют получить доступ к системным значкам по умолчанию, которые обеспечивают единообразное взаимодействие с пользователем в разных приложениях. Это не очень важно для кнопок «Добавить», «Редактировать», «Готово» и других текстовых кнопок, но может иметь большое значение для таких значков, как Compose:

tutorial_image
Рисунок 64: Тип элемента кнопки «Создать панель»

Далее нам нужно перевести нашу новую кнопку редактирования в сцену редактирования. При этом используется тот же процесс, что и при передаче толчка от ячейки главной таблицы к сцене детализации. Удерживая нажатой клавишу «Control», перетащите кнопку редактирования на новый контроллер навигации и выберите «Modal» для перехода «Действия». Вы должны увидеть новое segue-соединение с модальной иконкой на нем:

tutorial_image
Рисунок 65: Создание модального перехода

Как и для всех сегментов, нашему новому модальному переходу нужен уникальный идентификатор. Выберите значок модального перехода и введите editDetail в поле «Идентификатор» инспектора атрибутов.
Теперь вы сможете скомпилировать проект (с несколькими предупреждениями) и запустить пустую сцену редактирования, нажав кнопку «Редактировать» в сцене детализации. Нашей следующей задачей будет добавление некоторых компонентов пользовательского интерфейса в сцену редактирования вместе с кнопками «Отмена» и «Сохранить».

Далее мы собираемся разработать сцену редактирования. Он будет очень похож на детальную сцену, за исключением того, что он будет иметь текстовые поля вместо меток. Наша первая задача — добавить заголовок на панель навигации. Дважды щелкните центр навигационной панели сцены редактирования и введите «Редактировать». После этого сцена должна выглядеть следующим образом:

tutorial_image
Рисунок 66: Добавление заголовка в сцену редактирования

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

tutorial_image
Рисунок 67: Выбор объекта Table View

Затем измените поле «Контент» инспектора атрибутов на «Статические ячейки». Удалите все, кроме одной из статических ячеек, которые появляются в редакторе сцены. Также неплохо изменить поле «Выделение» на «Нет выделения», поскольку мы используем таблицу только для макета.

Теперь мы не можем использовать какие-либо значения стиля по умолчанию для ячеек, поскольку ни одно из них не использует текстовые поля. Вместо этого мы создадим ячейку с нуля. Сначала перетащите объект Label и Text Field в оставшуюся ячейку и используйте направляющие, чтобы убедиться, что они центрированы вертикально. Вы также должны изменить размер как метки, так и текстового поля, чтобы они выглядели примерно так:

tutorial_image
Рисунок 68: Добавление метки и текстового поля к сцене редактирования

Для сцены детализации мы указали Левую деталь для ячейки Стиль. Это автоматически определило стиль, шрифт и выравнивание компонентов, но поскольку мы создаем пользовательскую ячейку, нам нужно сделать это самим. Все эти параметры можно определить в инспекторе атрибутов для объектов UILabel и UITextField. Для метки измените текст на Имя, а затем установите цвет, совпадающий с метками заголовка в сцене детализации. Один из способов сделать это — открыть панель «Цвета» для метки редактируемой сцены, выбрать увеличительное стекло (которое на самом деле больше похоже на капельницу) и выбрать цвет из метки заголовка сцены. Выбранный цвет должен быть таким, как на следующем рисунке:

tutorial_image
Рисунок 69: Инструмент «капельница» на панели «Цвета»

Наконец, измените шрифт на System Bold с размером 12 и измените выравнивание на Right. Окончательные настройки показаны на следующем снимке экрана:

tutorial_image
Рисунок 70: Окончательные атрибуты для метки

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

tutorial_image
Рисунок 71: Ячейки таблицы редактирования сцены с соответствующими метками

Вам также следует изменить поле «Клавиатура» для текстового поля «Телефон» на «Цифровая панель», чтобы вместо цифровой клавиатуры отображалась цифровая клавиатура. Это охватывает таблицу редактирования сцены, но если вы попытаетесь скомпилировать проект прямо сейчас, вы заметите, что все эти ячейки исчезают. Это связано с тем, что EditViewController.m, предоставляемый шаблоном класса, определяет несколько методов источника данных, которые обрабатывают таблицу как ячейку прототипа. Мы удалим их в следующем разделе.

Но прежде чем сделать это, давайте добавим две кнопки на панель навигации, чтобы пользователи могли выбирать, хотят ли они отменить или сохранить свои изменения. Перетащите два элемента панели кнопок из библиотеки объектов на любую сторону панели навигации. Измените поле идентификатора левой кнопки на «Отмена», а правое — «Сохранить».

tutorial_image
Рисунок 72: Готовый макет для сцены редактирования

Обратите внимание, что кнопка «Сохранить» ярко-синего цвета в соответствии с соглашениями iOS UX. Опять же, эти идентификаторы по умолчанию помогают обеспечить согласованный пользовательский интерфейс между приложениями.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
// EditViewController.h
#import <UIKit/UIKit.h>
 
@interface EditViewController : UITableViewController
 
@property (strong, nonatomic) id detailItem;
 
@property (weak, nonatomic) IBOutlet UITextField *firstNameField;
@property (weak, nonatomic) IBOutlet UITextField *lastNameField;
@property (weak, nonatomic) IBOutlet UITextField *phoneNumberField;
@property (weak, nonatomic) IBOutlet UITextField *organizationField;
 
@end

Реализация очень похожа на DetailViewController.m. Все, что он делает, это проверяет, обновляются ли текстовые поля при изменении свойства detailItem:

01
02
03
04
05
06
07
08
09
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
// EditViewController.m
#import «EditViewController.h»
#import «Person.h»
 
@implementation EditViewController
 
@synthesize detailItem = _detailItem;
@synthesize firstNameField = _firstNameField;
@synthesize lastNameField = _lastNameField;
@synthesize phoneNumberField = _phoneNumberField;
@synthesize organizationField = _organizationField;
 
— (void)setDetailItem:(id)detailItem {
    if (_detailItem != detailItem) {
        _detailItem = detailItem;
        [self configureView];
    }
}
 
— (void)configureView {
    if (self.detailItem && [self.detailItem isKindOfClass:[Person class]]) {
        self.firstNameField.text = [self.detailItem firstName];
        self.lastNameField.text = [self.detailItem lastName];
        self.phoneNumberField.text = [self.detailItem phoneNumber];
        self.organizationField.text = [self.detailItem organization];
    }
}
 
— (void)viewDidLoad {
    [super viewDidLoad];
    [self configureView];
}
 
— (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
 
@end

Далее нам нужно подготовить текстовое поле делегата. В EditViewController.h скажите классу принять протокол UITextFieldDelegate со следующей строкой:

1
@interface EditViewController : UITableViewController <UITextFieldDelegate> As in the previous chapter, we can dismiss the keyboard by implementing the textFieldShouldReturn: method.

Напомним, что prepareForSegue: sender: метод вызывается на исходной сцене непосредственно перед тем, как iOS переключится на конечную сцену. Как и в основной сцене, мы будем использовать это для отправки выбранного элемента в сцену редактирования. В DetailViewController.m добавьте следующий метод:

1
2
3
4
5
6
7
— (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([[segue identifier] isEqualToString:@»editDetail»]) {
        NSArray *navigationControllers = [[segue destinationViewController] viewControllers];
        EditViewController *editViewController = [navigationControllers objectAtIndex:0];
        [editViewController setDetailItem:self.detailItem];
    }
}

Помните, что сцена редактирования встроена в контроллер навигации, поэтому модальный переход указывает на контроллер навигации, а не на саму сцену редактирования. Этот промежуточный контроллер навигации добавляет дополнительный шаг, о котором нам не нужно было беспокоиться в методе prepareForSegue: sender: главной сцены. Чтобы получить сцену редактирования, нам нужно запросить свойство viewControllers контроллера навигации, которое является массивом, содержащим его стек навигации. Поскольку сцена редактирования является единственным дочерним контроллером представления, мы можем получить к нему доступ через вызов objectAtIndex: 0. Получив экземпляр EditViewController, мы просто перенаправляем выбранный элемент из подробной сцены в сцену редактирования.

Вернемся к раскадровке, давайте соединим розетки и делегатов, которых мы только что показали. Для выходов выберите желтый значок в доке сцены редактирования, откройте инспектор соединений и перетащите из кругов firstNameField, lastNameField, organizationField и phoneNumberField в соответствующие текстовые поля в сцене.

tutorial_image
Рисунок 73: Создание выходных соединений

Чтобы установить EditViewController в качестве делегата для текстовых полей, выберите каждое текстовое поле, откройте инспектор соединений и перетащите его из круга делегата на желтый значок в док-станции, как показано на следующем снимке экрана. Сделайте это для каждого текстового поля.

tutorial_image
Рисунок 74: Создание соединений делегатов

Когда вы компилируете проект, вы сможете запустить сцену редактирования и увидеть текстовые поля, заполненные свойствами выбранного объекта Person. Надеюсь, к настоящему моменту вы относительно комфортно делаете такие розетки и делегируете соединения самостоятельно.

Вы можете редактировать значения, но поскольку мы еще не реализовали кнопки Отмена или Сохранить, вы не сможете изменить базовый объект Person или даже отойти от сцены редактирования.

Помните ту мастер-кнопку, которая автоматически появилась в навигационной панели подробной сцены? Контроллер навигации для сцен master / detail настроил для нас эту кнопку «назад», но, поскольку мы используем модальный переход, нам нужно вручную отклонить модально представленную сцену редактирования. Мы будем использовать то, что называется «раскрутить», чтобы вернуться к детализации сцены.

Основное различие между сеансом раскрутки и другими сегментами заключается в том, что первый использует существующую сцену в качестве места назначения, тогда как модальные и push-сегменты создают новый экземпляр своей сцены назначения. Это важно иметь в виду, если вы делаете много переходов назад и вперед.

Процесс раскручивания сцены также немного отличается от инициирования push-перехода. Он использует шаблон проектирования целевого действия, который мы обсуждали в предыдущей главе. В дополнение к вызову prepareForSegue: sender: метод на исходной сцене, раскрутка segue вызывает произвольный метод на целевой сцене (DetailViewController). Давайте продолжим и объявим действие отмены и сохранения в DetailViewController.h:

1
2
— (IBAction)save:(UIStoryboardSegue *)sender;
— (IBAction)cancel:(UIStoryboardSegue *)sender;

Вскоре мы добавим эти методы к кнопкам «Отмена» и «Сохранить» в сцене редактирования. Но сначала нам нужно их реализовать. Добавьте следующие методы в DetailViewController.m:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
— (IBAction)save:(UIStoryboardSegue *)segue {
    if ([[segue identifier] isEqualToString:@»saveInput»]) {
        EditViewController *editController = [segue sourceViewController];
        [self.detailItem setFirstName:editController.firstNameField.text];
        [self.detailItem setLastName:editController.lastNameField.text];
        [self.detailItem setPhoneNumber:editController.phoneNumberField.text];
        [self.detailItem setOrganization:editController.organizationField.text];
        [self configureView];
    }
}
 
— (IBAction)cancel:(UIStoryboardSegue *)segue {
    if ([[segue identifier] isEqualToString:@»cancelInput»]) {
        // Custom cancel handling can go here.
    }
}

Это довольно просто. Метод save: обновляет свойства detailItem на основе значений текстового поля из сцены редактирования, а затем обновляет его метки, вызывая configureView. Метод cancel: просто игнорирует все, что произошло в сцене редактирования.
Теперь мы можем создать последовательность действий, чтобы закрыть сцену редактирования и вызвать соответствующий метод. Настройка раскручивания сегментов аналогична созданию проталкиваемых сегментов: вы перетаскиваете управление из компонента пользовательского интерфейса, который инициирует переход к зеленому значку «Выход» в док-станции. Этот значок предназначен исключительно для создания раскручивающихся сегментов.

tutorial_image
Рисунок 75: Значок выхода в доке (крайний справа)

Таким образом, перетащите элемент управления с кнопки «Сохранить» в сцене редактирования на значок «Выход» на док-станции, как показано на следующем рисунке:

tutorial_image
Рисунок 76: Создание перехода для кнопки «Сохранить»

Появится меню с просьбой связать действие с переходом:

tutorial_image
Рисунок 77: Выбор действия для перехода

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

tutorial_image
Рисунок 78: Размотка сегментов в схеме документа

В отличие от push и модальных сегментов, раскручивающиеся сегменты не имеют визуального представления в конструкторе интерфейсов, поэтому контур документа — единственный способ выбрать их. Нашим последним шагом будет добавление уникальных идентификаторов в оба этих сегмента с помощью инспектора атрибутов. Используйте cancelInput для кнопки «Отмена» и saveInput для кнопки «Сохранить» (обратите внимание, что это идентификаторы, которые мы проверяли в методах cancel: и save: соответственно). Опять же, поскольку наше примерное приложение очень простое, добавление идентификаторов segue — это скорее шаг передовой практики, чем необходимость.

tutorial_image
Рисунок 79: Определение идентификаторов раскрутки

Вы можете думать о том, как раскрутиться, как комбинацию перехода и кнопки. Segue заботится об удалении сцены (то есть о переходе на родительскую сцену), но поскольку она инициируется нажатием кнопки, вы также можете прикрепить к ней метод, используя шаблон target-action.

Наша сцена редактирования завершена, и вы сможете скомпилировать проект, ввести значения в текстовые поля сцены редактирования и выбрать отмену или сохранение изменений. Поскольку метод save: вызывает configureView после сохранения новых значений, сцена сведений будет обновлена ​​для отражения изменений. Однако мы никогда не указывали мастер-сцене обновляться, поэтому ваши изменения не будут отражены в мастер-списке.

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

UIViewController определяет метод с именем viewWillAppear: и вызывает его непосредственно перед отображением связанной сцены. Это отличается от viewDidLoad :, который получает первый раз, когда представление отображается. Поскольку родительский контроллер навигации отображает один и тот же экземпляр главной сцены каждый раз, когда пользователь переходит к ней, нам нужно использовать viewWillAppear: метод вместо viewDidAppear :. В MasterViewController.m добавьте следующий метод:

1
2
3
4
5
— (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    UITableView *view = (UITableView *)self.view;
    [view reloadData];
}

Сначала мы передаем метод super, а затем извлекаем корневой экземпляр UIView контроллера через свойство view. Мы можем предположить, что это UITableView, потому что MasterViewController наследуется от UITableViewController, но нам все еще нужно привести его, чтобы не жаловаться компилятор. Метод reloadData UITableView регенерирует ячейки таблицы на основе базового набора данных (массив _objects), и теперь основной список должен отражать любые изменения, которые вы сохранили из сцены редактирования.

tutorial_image
Рисунок 80: Завершенная главная сцена

В этой главе мы узнали, как управлять несколькими сценами в одном приложении. Мы экспериментировали с UITableViewControllers, UINavigationControllers и всеми видами сегментов. Одна из наиболее важных концепций, которую следует извлечь из этой главы, заключается в том, как мы переносили данные между каждой сценой: с помощью метода prepareForSegue: sender: и методов save: и cancel: для перехода к следующей последовательности. Большинство приложений действительно просто удобные редакторы для сложных структур данных, поэтому понимание того, как эти данные передаются, имеет большое значение для эффективной организации проектов iOS.

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

Этот урок представляет собой главу из iOS, сжатой , бесплатной электронной книги от команды Syncfusion .