Что на самом деле делает addChildViewController?

102

Я просто впервые погружаюсь в разработку для iOS, и первое, что мне пришлось сделать, это реализовать настраиваемый контроллер представления контейнера (позвольте назвать его), SideBarViewControllerкоторый меняет местами, какой из нескольких возможных контроллеров представления дочерних элементов он отображается почти так же, как стандартный контроллер панели вкладок . (Это в значительной степени контроллер панели вкладок, но с скрытым боковым меню вместо панели вкладок.)

Согласно инструкциям в документации Apple, я вызываю addChildViewControllerкаждый раз, когда добавляю дочерний ViewController в свой контейнер. Мой код для замены текущего контроллера дочернего представления отображается SideBarViewControllerследующим образом:

- (void)showViewController:(UIViewController *)newViewController {
    UIViewController* oldViewController = [self.childViewControllers 
                                           objectAtIndex:0];
    
    [oldViewController removeFromParentViewController];
    [oldViewController.view removeFromSuperview];
    
    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self addChildViewController: newViewController];
    [self.view addSubview: newViewController.view];
}

Затем я начал пытаться понять, что addChildViewControllerздесь происходит, и понял, что понятия не имею. Кроме втыкания нового ViewControllerв .childViewControllersмассив, похоже, ни на что не влияет. Действия и выходы из представления дочернего контроллера в дочерний контроллер, который я установил на раскадровке, по-прежнему работают нормально, даже если я никогда не вызываю addChildViewController, и я не могу представить, на что еще это могло повлиять.

В самом деле, если я перепишу свой код, чтобы он не звонил addChildViewController, а вместо этого выглядел бы так ...

- (void)showViewController:(UIViewController *)newViewController {

    // Get the current child from a member variable of `SideBarViewController`
    UIViewController* oldViewController = currentChildViewController;

    [oldViewController.view removeFromSuperview];

    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self.view addSubview: newViewController.view];

    currentChildViewController = newViewController;
}

... тогда мое приложение по-прежнему работает отлично, насколько я могу судить!

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

Добавляет заданный контроллер представления как дочерний. ... Этот метод предназначен только для вызова реализацией настраиваемого контроллера представления контейнера. Если вы переопределите этот метод, вы должны вызвать super в своей реализации.

Также есть этот абзац ранее на той же странице:

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

Вот основные методы, которые вам может понадобиться вызвать:

addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:

но он не дает никаких подсказок относительно того, какие «события» или «ожидаемое поведение сдерживания», о котором идет речь, или почему (или даже когда) вызов этих методов «необходим».

Все примеры настраиваемых контроллеров представления контейнеров в разделе «Настраиваемые контроллеры представления контейнеров» документации Apple вызывают этот метод, поэтому я предполагаю, что он служит какой-то важной цели, помимо простого добавления дочернего ViewController в массив, но я не могу понять что это за цель. Что делает этот метод и почему я должен его называть?

Марк Эмери
источник
3
На странице видео Apple WWDC 2011 года есть отличная сессия («Реализация ограничения UIViewController») по этой теме.
Alladinian

Ответы:

94

Меня тоже интересовал этот вопрос. Я смотрел сеанс 102 видео WWDC 2011, и мистер View Controller Брюс Д. Нило сказал следующее:

viewWillAppear:, viewDidAppear:и т. д. не при чем addChildViewController:. Все, что addChildViewController:нужно сделать, это сказать: «Этот контроллер представления является его потомком», и это не имеет ничего общего с внешним видом представления. Когда они вызываются, это связано с тем, когда представления входят и выходят из иерархии окон.

Кажется, что призыв к addChildViewController:очень мало делает. Побочные эффекты звонка - важная часть. Они исходят из отношений parentViewControllerи childViewControllersотношений. Вот некоторые из известных мне побочных эффектов:

  • Перенаправление методов внешнего вида на дочерние контроллеры представления
  • Способы переадресации ротации
  • (Возможно) пересылка предупреждений памяти
  • Избегайте несовместимых иерархий VC, особенно transitionFromViewController:toViewController:…если у обоих VC должен быть один и тот же родительский элемент.
  • Разрешение настраиваемым контроллерам представления контейнера принимать участие в сохранении и восстановлении состояния
  • Участие в цепочке респондентов
  • Подключение к navigationController, tabBarControllerи т.д. свойства
Неванский король
источник
Это сессия 102, а не 101
SeanChense
+1 для цепочки респондентов. addChildViewController требуется, если вы хотите получать события касания в подпредставлении, принадлежащем дочернему UIViewController
charlieb
108

Я думаю, что пример стоит тысячи слов.

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

введите описание изображения здесь

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

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

введите описание изображения здесь

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

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

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

Изменить: есть две категории событий, которые перенаправляются дочерним контроллерам представления:

1- Способы появления:

- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:

2- Методы вращения:

- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:

Вы также можете контролировать, какие категории событий вы хотите пересылать автоматически, переопределив shouldAutomaticallyForwardRotationMethodsи shouldAutomaticallyForwardAppearanceMethods.

Хиджази
источник
Из документации и после быстрого тестирования я не думаю, что есть какое-либо другое событие, которое будет перенаправлено только addChildViewControllerна родительский контроллер.
Hejazi
желаю, чтобы он автоматически перенаправлялся viewWillLayoutSubviews
MobileMon
10

-[UIViewController addChildViewController:]добавляет только переданный в представление контроллер в массив viewController, на который viewController (родительский элемент) хочет сохранить ссылку. Фактически вы должны сами добавить эти представления viewController на экран, добавив их в качестве подвидов другого представления (например, представления parentViewController). В Interface Builder также есть удобный объект для использования childrenViewControllers в раскадровках.

Раньше, чтобы сохранять ссылки на другие viewControllers, представления которых вы использовали, вам приходилось вручную ссылаться на них в @properties. Наличие встроенного свойства, такого как childViewControllersи, следовательно, parentViewControllerявляется удобным способом управления такими взаимодействиями и создания составных контроллеров представления, таких как UISplitViewController, которые вы найдете в приложениях для iPad.

Более того, childrenViewControllers также автоматически получает все системные события, которые получает родитель: -viewWillAppear, -viewWillDisappear и т. Д. Раньше вы должны были вызывать эти методы вручную для своих «childrenViewControllers».

Вот и все.

Джанлука Транчедоне
источник
На чем вы думаете, что это все, что он делает? Кроме того, можете ли вы предоставить список «системных событий», которые получает ребенок? Поиск в Google iOS "system events"ничего не дает; похоже, это не тот термин, который использует Apple?
Марк Эмери
По сути, это удобный метод, который позволяет вам добавить представление View Controller B в качестве подпредставления View Controller A, но при этом View Controller B по-прежнему управляет своим представлением. Чтобы это работало правильно, необходимо убедиться, что контроллер представления B получает системные события (см. Обратные вызовы UIViewControllerDelegate). addChildViewController подключает это за вас, чтобы избавить вас от необходимости передавать все вручную.
Sam Clewlow