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

Я просто впервые погружаюсь в разработку под 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 класса UIViewController, в настоящее время:

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

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

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

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

addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:

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

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

Ответ 1

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

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

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

  • Переадресация методов внешнего вида на дочерние контроллеры.
  • Способы форвардинга вращения
  • (возможно) пересылка предупреждений памяти
  • Предотвращение несогласованных VC-иерархий, особенно в transitionFromViewController:toViewController:…, где оба VC должны иметь один и тот же родительский
  • Предоставление пользователям настраиваемых контроллеров просмотра контейнеров в состоянии сохранения и восстановления состояния.
  • Участие в цепочке ответчиков
  • Включение свойств navigationController, tabBarController и т.д.

Ответ 2

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

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

enter image description here

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

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

enter image description here

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

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

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

Edit: Существуют две категории событий, которые перенаправляются на контроллеры дочерних элементов:

1- Методы внешнего вида:

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

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

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

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

Ответ 3

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

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

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

Что это.