Создание пользовательских контейнерных контроллеров представления
Контейнерные контроллеры представления являются критической частью проекта приложения для iOS. Они позволяют Вам анализировать свое приложение в меньшие и более простые части, каждый, которым управляет контроллер представления, выделенный той задаче. Контейнеры позволяют этим контроллерам представления сотрудничать для представления бесшовного интерфейса.
iOS обеспечивает много стандартных контейнеров, чтобы помочь Вам организовать свои приложения. Однако иногда необходимо создать пользовательский поток операций, не соответствующий что предоставленный любым из системных контейнеров. Возможно, в Вашем видении, для Вашего приложения нужна определенная организация дочерних контроллеров представления специализированными жестами навигации или переходами анимации между ними. Чтобы сделать это, Вы реализуете пользовательский контейнер.
Разработка контейнерного контроллера представления
Большинством способов контейнерный контроллер представления точно так же, как довольное контроллер представления. Это управляет представлениями и содержанием, координатами с другими объектами в Вашем приложении, и реагирует на события в цепочке респондента. Прежде, чем разработать контейнерный контроллер, необходимо уже быть знакомы с разработкой контроллеров представления содержания. Вопросы о проекте в Создании Пользовательского Довольный Контроллеры Представления также применяются при создании контейнеров.
При разработке контейнера Вы создаете явные отношения отцов и детей между своим контейнером, родителем, и другими контроллерами представления, его дочерними элементами. Более в частности рисунок 14-1 показывает, что существуют явные соединения между представлениями также. Ваш контейнер добавляет представления содержания других контроллеров представления в его собственной иерархии представления. Каждый раз, когда представление дочернего элемента выведено на экран в иерархии представления контейнера, Ваш контейнер также устанавливает соединение с дочерним контроллером представления и гарантирует, что все надлежащие события контроллера представления отправляются дочернему элементу.
Ваш контейнер должен установить правила, и его дочерние элементы должны следовать за ними; когда содержание дочернего элемента видимо в своей собственной иерархии представления, это до родителя для решения. Контейнер решает, где в иерархии, что представление помещается и как это измерено и расположено там. Этот принцип разработки не отличается от того из довольного контроллер представления. Контроллер представления ответственен за управление его собственной иерархией представления, и другие классы никогда не должны управлять его содержанием. Где необходимо, Ваш контейнерный класс может представить открытые методы и свойства, чтобы позволить его поведению управляться. Например, если другой объект должен быть в состоянии сказать Вашему контейнеру отображать новое представление, то Ваш контейнерный класс должен представить открытый метод позволить этому переходу происходить. Фактическая реализация, изменяющая иерархию представления, должна быть в контейнерном классе. Этот руководящий принцип чисто разделяет ответственность между контейнером и его дочерними элементами, всегда делая каждый контроллер представления ответственным за его собственную иерархию представления.
Вот некоторые конкретные вопросы, на которые необходимо быть в состоянии ответить о контейнерном классе:
Какова роль контейнера и какую роль его дочерние элементы играют?
Между одноуровневыми элементами существует ли отношение?
Как дочерние контроллеры представления добавлены к или удалены из контейнера? Ваш контейнерный класс должен обеспечить общедоступные свойства и методы, чтобы позволить дочерним элементам быть выведенными на экран им.
Сколько дочерних элементов выведено на экран контейнером?
Содержание контейнерных помех или динамично? В статическом проекте более или менее фиксируются дочерние элементы, тогда как в динамическом проекте, могут произойти переходы между одноуровневыми элементами. Вы определяете то, что инициировало переход к новому одноуровневому элементу. Это могло бы быть программируемо, или это могло бы произойти, когда пользователь взаимодействует с контейнером.
Контейнеру принадлежит какое-либо из его собственных представлений? Например, пользовательский интерфейс Вашего контейнера может включать информацию о дочернем контроллере представления или средствах управления для разрешения навигации.
Делает контейнер, требуют, чтобы его дочерние элементы обеспечили методы или свойства кроме найденных на
UIViewController
класс? Существует много причин, почему контейнер мог бы сделать это. Этому, возможно, понадобилась бы определенная информация от дочернего элемента к используемому для конфигурирования других аспектов контейнерного дисплея, или это могло бы позволить дочернему элементу изменять поведение контейнера. Когда контейнерно-специфичные события имеют место, это даже могло бы вызвать дочерний контроллер представления.Ваш контейнер позволяет его поведению быть сконфигурированным?
Все его дочерние элементы обрабатываются тождественно, или это имеет многократные типы дочерних элементов, каждого со специализированными способами поведения? Например, Вы могли бы создать контейнер, выводящий на экран два дочерних элемента, координируя действия между этими двумя дочерними элементами. Каждый дочерний элемент реализует отличный набор методов, чтобы позволить его поведению быть сконфигурированным.
Таким образом, контейнерный контроллер часто имеет больше отношений с другими объектами (особенно другие контроллеры представления), чем контроллер содержания. Так, необходимо приложить дополнительные усилия к пониманию, как работает контейнер. Идеально, как с контроллером содержания, Вы хотите скрыть многие из тех способов поведения позади превосходного общедоступного класса API.
Примеры общих контейнерных проектов
Самый простой способ понять, как разработать новый контейнерный класс, состоит в том, чтобы исследовать поведение и общедоступный API существующих системных контейнерных классов. Каждый определяет его собственную метафору навигации, и интерфейс программирования раньше конфигурировал его. Этот раздел смотрит на несколько из этих классов с точки зрения контейнерного проекта. Это не предоставляет полное описание интерфейса программирования каждого класса, но просто смотрит на некоторые критические понятия. Для получения дальнейшей информации об использовании этих системных контейнеров, см. Каталог Контроллера Представления для iOS.
Контроллер навигации управляет штабелем дочерних контроллеров представления
Контроллер навигации позволяет последовательности отличных экранов интерфейса пользователя быть выведенной на экран пользователю. Метафора, используемая контроллером навигации, является штабелем дочерних контроллеров представления. Самое верхнее представление контроллера представления помещается в иерархию представления контроллера навигации. Для отображения нового контроллера представления Вы продвигаете его на штабель. Когда Вы сделаны, Вы удаляете контроллер представления из штабеля.
Рисунок 14-2 показывает, что представление только единственного дочернего элемента видимо и что представление дочернего элемента является частью более сложной иерархии представлений, предоставленных контроллером навигации.
Когда контроллер представления продвинут на или вытолкан от штабеля, переход может быть анимирован, что означает, что представления двух дочерних элементов кратко выведены на экран вместе. В дополнение к дочерним представлениям контроллер навигации также включает свои собственные представления содержания для отображения панели навигации. Содержание панели навигации обновляется на основе выводимого на экран дочернего элемента.
Вот некоторые важные методы и свойства что UINavigationController
использование класса для определения его поведения:
topViewController
состояния свойства, которые контроллер наверху штабеля.viewControllers
списки свойств все дочерние элементы в штабеле.pushViewController:animated:
метод продвигает новый контроллер представления на штабеле. Этот метод выполняет всю работу, необходимую для обновления иерархии представления для отображения представления нового дочернего элемента.popViewControllerAnimated:
метод удаляет контроллер вида сверху из штабеля.delegate
когда изменения состояния происходят, свойство позволяет клиенту контейнера быть уведомленным.
Контроллер навигации использует свойства на дочернем контроллере представления для корректировки содержания, которое это выводит на экран. Эти свойства определяются UIViewController
базовый класс так, чтобы некоторое поведение по умолчанию было доступно; это позволяет любому контроллеру представления быть сделанным дочерним элементом контроллера навигации. Вот некоторые свойства, которые ищет контроллер навигации:
navigationItem
свойство обеспечивает содержание панели навигации.toolbarItems
свойство обеспечивает содержание нижней панели.editButtonItem
свойство обеспечивает доступ к представлению в элементе навигации так, чтобы контроллер навигации мог переключить режим редактирования дочернего представления.
Контроллер панели вкладок использует набор дочерних контроллеров
Контроллер представления вкладки позволяет ряду отличных экранов интерфейса пользователя быть выведенным на экран пользователю. Однако вместо штабеля контроллеров представления, контроллер представления вкладки использует простой массив. Рисунок 14-3 показывает, что снова, представление контроллера представления только одного дочернего элемента выведено на экран за один раз. Однако к этим представлениям не должны получать доступ последовательно, и переход к новому дочернему элементу обычно не анимируется.
Вот являются некоторые важные методы и свойства этим UITabBarController
использование класса, чтобы позволить приложениям управлять тем, что выводит на экран контроллер панели вкладок:
viewControllers
свойство содержит список дочерних контроллеров представления, действующих как вкладки содержания.selectedViewController
свойство позволяет Вам читать или изменяться, какой дочерний элемент видим.delegate
когда изменения состояния происходят, свойство позволяет клиенту контейнера быть уведомленным.
Контроллер панели вкладок использует дочерний элемент tabBarItem
свойство, чтобы определить, как это выведено на экран на надлежащей вкладке.
Контроллер страницы использует источник данных для обеспечения новых дочерних элементов
Контроллер страницы использует страницы содержания как его метафора, как страницы книги. Каждая страница, выведенная на экран контейнером, предоставлена дочерним контроллером представления.
Книги могут иметь много страниц — намного больше чем число экранов содержания в контроллере навигации — настолько сохраняющий все страницы в памяти сразу может не быть возможным. Вместо этого контроллер просмотра сохраняет дочерние контроллеры для видимых страниц и выбирает другие страницы по требованию. Когда пользователь хочет видеть новую страницу, контейнер вызывает объект, связанный с dataSource
свойство для получения нового контроллера. Таким образом контроллер просмотра с помощью источника данных использует модель приема вместо того, чтобы иметь приложение, непосредственно продвигают новые страницы на себя.
Контроллер просмотра может также быть настроен для различных видов разметок книги. Число страниц и размер страниц могут отличаться. Вот два ключевых свойства, влияющие на поведение контроллера просмотра:
spineLocation
свойство определяет, как организованы страницы. Некоторые разметки только выводят на экран одну страницу за один раз. Другие разметки выводят на экран многократные страницы.transitionStyle
свойство определяет, как анимированы переходы между страницами.
Реализация пользовательского контейнерного контроллера представления
Как только Вы разработали поведение своего класса и определили много аспектов его общедоступного API, Вы готовы начать реализовывать контейнер. Цель реализации контейнера состоит в том, чтобы быть в состоянии добавить другое представление контроллера представления (и связанная иерархия представления) как поддерево в иерархии представления Вашего контейнера. Дочерний элемент остается ответственным за его собственную иерархию представления, сохраните для того, где контейнер решает поместить его на экране. Когда Вы добавляете представление дочернего элемента, необходимо гарантировать, чтобы события продолжали быть распределенными обоим контроллерам представления. Вы делаете это путем явного соединения нового контроллера представления как дочерний элемент контейнера.
UIViewController
класс обеспечивает методы, которые контейнерный контроллер представления использует для управления отношением между собой и его дочерними элементами. Полный список методов и свойств находится в ссылке; посмотрите Управляющие Дочерние Контроллеры Представления в Пользовательском Контейнере
Добавление и удаление дочернего элемента
Перечисление 14-1 показывает типичную реализацию, добавляющую контроллер представления как дочерний элемент другого контроллера представления. Каждый пронумерованный шаг в перечислении описан более подробно после перечисления.
Перечисление 14-1 , Добавляющее другое представление контроллера представления к иерархии представления контейнера
- (void) displayContentController: (UIViewController*) content; |
{ |
[self addChildViewController:content]; // 1 |
content.view.frame = [self frameForContentController]; // 2 |
[self.view addSubview:self.currentClientView]; |
[content didMoveToParentViewController:self]; // 3 |
} |
Вот то, что делает код:
Это вызывает контейнер
addChildViewController:
метод для добавления дочернего элемента. ВызовaddChildViewController:
метод также вызывает дочерний элементwillMoveToParentViewController:
метод автоматически.Это получает доступ к дочернему элементу
view
свойство для получения представления и добавляет его к своей собственной иерархии представления. Контейнер устанавливает размер и позицию дочернего элемента прежде, чем добавить представление; контейнеры всегда выбирают, где появляется содержание дочернего элемента. Несмотря на то, что этот пример делает это путем явной установки кадра, Вы могли также использовать ограничения макета для определения позиции представления.Это явно вызывает дочерний элемент
didMoveToParentViewController:
метод, чтобы сигнализировать, что работа завершена.
В конечном счете Вы хотите быть в состоянии удалить представление дочернего элемента из иерархии представления. В этом случае, показанный в Перечислении 14-2, Вы выполняете шаги наоборот.
Перечисление 14-2 , Удаляющее другое представление контроллера представления к иерархии представления контейнера
- (void) hideContentController: (UIViewController*) content |
{ |
[content willMoveToParentViewController:nil]; // 1 |
[content.view removeFromSuperview]; // 2 |
[content removeFromParentViewController]; // 3 |
} |
Вот то, что делает этот код:
Вызывает дочерний элемент
willMoveToParentViewController:
метод с параметромnil
сказать дочернему элементу, что это удаляется.Очищает иерархию представления.
Вызывает дочерний элемент
removeFromParentViewController
метод для удаления его из контейнера. ВызовremoveFromParentViewController
метод автоматически вызывает дочерний элементdidMoveToParentViewController:
метод.
Для контейнера с чрезвычайно статическим содержанием, добавляя и демонтируя контроллеры представления так просто. Каждый раз, когда Вы хотите добавить новое представление, добавьте новый контроллер представления как дочерний элемент сначала. После того, как представление удалено, удалите дочерний элемент из контейнера. Однако иногда Вы хотите анимировать новый дочерний элемент на экран, одновременно удаляя другой дочерний элемент. Перечисление 14-3 показывает пример того, как сделать это.
Перечисление 14-3 , Переходящее между двумя контроллерами представления
- (void) cycleFromViewController: (UIViewController*) oldC |
toViewController: (UIViewController*) newC |
{ |
[oldC willMoveToParentViewController:nil]; // 1 |
[self addChildViewController:newC]; |
newC.view.frame = [self newViewStartFrame]; // 2 |
CGRect endFrame = [self oldViewEndFrame]; |
[self transitionFromViewController: oldC toViewController: newC // 3 |
duration: 0.25 options:0 |
animations:^{ |
newC.view.frame = oldC.view.frame; // 4 |
oldC.view.frame = endFrame; |
} |
completion:^(BOOL finished) { |
[oldC removeFromParentViewController]; // 5 |
[newC didMoveToParentViewController:self]; |
}]; |
} |
Вот то, что делает этот код:
Запускает оба перехода контроллера представления.
Вычисляет две новых позиции кадра раньше выполняли анимацию перехода.
Вызовы
transitionFromViewController:toViewController:duration:options:animations:completion:
метод для выполнения подкачки. Этот метод автоматически добавляет новое представление, выполняет анимацию, и затем удаляет старое представление.Шаг анимации для выполнения для подкачивания представлений.
Когда переход завершается, иерархия представления находится в своем конечном состоянии, таким образом, это заканчивает работу путем отправки заключительных двух уведомлений.
Настройка поведения обратного вызова появления и вращения
Как только Вы добавляете дочерний элемент к контейнеру, контейнер автоматически вперед вращение и обратные вызовы появления к дочерним контроллерам представления, как только событие имеет место, который требует, чтобы было передано сообщение. Это обычно - поведение, которое Вы хотите, потому что оно гарантирует, что должным образом отправляются все события. Однако иногда поведение по умолчанию может отправить те события в порядке, который не целесообразен для Вашего контейнера. Например, если многократные дочерние элементы одновременно изменяют свое состояние отображения, можно хотеть консолидировать изменения так, чтобы обратные вызовы появления все произошли одновременно в более логическом порядке. Чтобы сделать это, Вы изменяете свой контейнерный класс для взятия на себя ответственности за обратные вызовы появления или вращения.
Для принятия управления обратных вызовов появления Вы переопределяете shouldAutomaticallyForwardAppearanceMethods
метод для возврата NO
. Перечисление 14-4 показывает необходимый код.
Перечисление 14-4 , Запрещающее автоматическую передачу появления
- (BOOL) shouldAutomaticallyForwardAppearanceMethods |
{ |
return NO; |
} |
Чтобы фактически сообщить дочернему контроллеру представления, что переход появления происходит, Вы вызываете дочерний элемент beginAppearanceTransition:animated:
и endAppearanceTransition
методы.
Если Вы принимаете отправку этих сообщений, Вы также ответственны за передачу их дочерним элементам, когда Ваш контейнерный контроллер представления появляется и исчезает. Например, если Вашему контейнеру сослался на единственный дочерний элемент a child
свойство, Ваш контейнер передал бы эти сообщения дочернему элементу, как показано в Перечислении 14-5.
Перечисление 14-5 , Передающее сообщения появления, когда контейнер появляется или исчезает
-(void) viewWillAppear:(BOOL)animated |
{ |
[self.child beginAppearanceTransition: YES animated: animated]; |
} |
-(void) viewDidAppear:(BOOL)animated |
{ |
[self.child endAppearanceTransition]; |
} |
-(void) viewWillDisappear:(BOOL)animated |
{ |
[self.child beginAppearanceTransition: NO animated: animated]; |
} |
-(void) viewDidDisappear:(BOOL)animated |
{ |
[self.child endAppearanceTransition]; |
} |
Передача работ событий вращения почти тождественно и может быть сделана независимо от передачи сообщений появления. Во-первых, Вы переопределяете shouldAutomaticallyForwardRotationMethods
метод для возврата NO
. Затем время от времени надлежащий Вашему контейнеру, Вы вызываете следующие методы:
Практические предложения для создания контейнерного контроллера представления
Разработка, разрабатывая и тестируя новый контейнерный контроллер представления занимает время. Несмотря на то, что отдельные способы поведения являются прямыми, контроллер в целом может быть довольно сложным. Рассмотрите часть следующего руководства при реализации собственных контейнерных классов:
Разработайте контроллер представления сначала как довольное контроллер представления, с помощью регулярных представлений, принадлежавших контейнеру. Это позволяет Вам фокусироваться на получении расположения и переходов анимации, корректных, одновременно не будучи должен управлять отношениями отцов и детей.
Никогда не получайте доступ ни к какому представлению кроме представления верхнего уровня дочернего контроллера представления. Точно так же у дочерних элементов должно быть только минимальное знание того, что родитель делает с представлением; не представляйте ненужные подробные данные дочернему элементу.
Если контейнеру нужен дочерний элемент для объявления методов или свойств, он должен определить протокол для осуществления этого:
@protocol MyContentContainerProtocol <NSObject>
...
@end
- (void) displayContentController: (UIViewController<MyContentContainerProtocol>*) content;