Что на самом деле делает 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
Ссылка на класс в настоящее время:
Добавляет данный контроллер представления как дочерний элемент.... Этот метод предназначен для вызова только реализацией пользовательского контроллера представления контейнера. Если вы переопределите этот метод, вы должны вызвать super в своей реализации.
Там также этот абзац ранее на той же странице:
Контроллер представления контейнера должен связать дочерний контроллер представления с самим собой, прежде чем добавлять дочернее корневое представление в иерархию представлений. Это позволяет iOS правильно направлять события на дочерние контроллеры представления и представления, которыми управляют эти контроллеры. Аналогично, после того, как он удаляет дочернее корневое представление из своей иерархии представлений, он должен отключить этот дочерний контроллер представления от самого себя. Чтобы создать или разорвать эти ассоциации, ваш контейнер вызывает определенные методы, определенные базовым классом. Эти методы не предназначены для вызова клиентами вашего контейнерного класса; они должны использоваться только реализацией вашего контейнера для обеспечения ожидаемого поведения сдерживания.
Вот основные методы, которые вам могут понадобиться вызвать:
addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:
но он не дает никакого представления о том, что представляют собой "события" или "ожидаемое поведение сдерживания", о которых идет речь, или почему (или даже когда) вызов этих методов является "существенным".
Все примеры пользовательских контроллеров представления контейнера в разделе "Настраиваемые контроллеры представления контейнера" в документации Apple вызывают этот метод, поэтому я предполагаю, что он служит какой-то важной цели, помимо простого помещения дочернего ViewController в массив, но я не могу понять что это за цель. Что делает этот метод, и почему я должен называть его?
4 ответа
Мне тоже было интересно об этом вопросе. Я смотрел сессию 102 видео WWDC 2011, и мистер View Controller, Брюс Д. Нило, сказал следующее:
viewWillAppear:
,viewDidAppear:
и т.д. не имеют ничего общего сaddChildViewController:
, Все этоaddChildViewController:
Это значит сказать: "Этот контроллер представления является дочерним по отношению к этому" и не имеет ничего общего с внешним видом представления. Когда их вызывают, это связано с тем, когда представления перемещаются в и из оконной иерархии.
Так что кажется, что призыв к addChildViewController:
очень мало Побочные эффекты вызова являются важной частью. Они приходят из parentViewController
а также childViewControllers
отношения. Вот некоторые из побочных эффектов, которые я знаю:
- Пересылка методов внешнего вида дочерним контроллерам представления
- Методы переадресации
- (Возможно) пересылка предупреждений памяти
- Избегать противоречивых иерархий ВК, особенно в
transitionFromViewController:toViewController:…
где оба VC должны иметь одного и того же родителя - Разрешение контроллерам пользовательских контейнерных представлений участвовать в сохранении и восстановлении состояния
- Участие в цепочке респондента
- Подключать
navigationController
,tabBarController
и т. д. свойства
Я думаю, что пример стоит тысячи слов.
Я работал над приложением библиотеки и хотел показать хороший вид блокнота, который появляется, когда пользователь хочет добавить заметку.
Попробовав несколько решений, я в итоге изобрел свое собственное решение для показа блокнота. Поэтому, когда я хочу показать блокнот, я создаю новый экземпляр NotepadViewController
и добавьте его корневое представление как подпредставление к основному представлению. Все идет нормально.
Тогда я заметил, что изображение блокнота частично скрыто под клавиатурой в ландшафтном режиме.
Поэтому я хотел изменить изображение блокнота и сдвинуть его вверх. И для этого я написал правильный код в willAnimateRotationToInterfaceOrientation:duration:
метод, но когда я запустил приложение, ничего не произошло! И после отладки я заметил, что ни один из UIViewController
методы вращения на самом деле называется в NotepadViewController
, Только те методы в главном контроллере представления вызываются.
Чтобы решить эту проблему, мне нужно было вызвать все методы из NotepadViewController
вручную, когда они вызываются в главном контроллере вида. Это скоро усложнит ситуацию и создаст дополнительную зависимость между несвязанными компонентами в приложении.
Это было в прошлом, до того, как появилась концепция дочерних контроллеров представления. Но теперь вам нужно только addChildViewController
к контроллеру основного вида, и все будет работать как положено, без дополнительной ручной работы.
Редактировать: есть две категории событий, которые передаются дочерним контроллерам представления:
1- Методы внешнего вида:
- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:
2- Методы вращения:
- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:
Вы также можете контролировать, какие категории событий вы хотите перенаправлять автоматически, переопределяя shouldAutomaticallyForwardRotationMethods
а также shouldAutomaticallyForwardAppearanceMethods
,
-[UIViewController addChildViewController:]
добавляет только переданный в view контроллер в массив viewControllers, на который viewController (родитель) хочет сохранить ссылку. Вы должны добавить эти представления viewController на экран самостоятельно, добавив их как подпредставления другого представления (например, представления parentViewController). В Интерфейсном Разработчике также есть удобный объект для использования childrenViewControllers в Раскадровках.
Ранее, чтобы хранить ссылки на другие viewControllers, для которых вы использовали представления, вы должны были вручную ссылаться на них в @properties. Наличие встроенного свойства, такого как childViewControllers
и следовательно parentViewController
это удобный способ управлять такими взаимодействиями и создавать составные контроллеры вида, такие как UISplitViewController, который вы найдете в приложениях для iPad.
Более того, childrenViewControllers также автоматически получает все системные события, которые получает родительский объект: -viewWillAppear, -viewWillDisappear и т. Д. Ранее вы должны были вызывать эти методы вручную для ваших "childrenViewControllers".
Вот и все.
При выполнении сдерживания контроллера представления вам необходимо синхронизировать иерархию представления с иерархией контроллера представления, выполнив и:
- (void)showViewController:(UIViewController *)newViewController {
UIViewController* oldViewController = [self.childViewControllers objectAtIndex:0];
[oldViewController willMoveToParentViewController:nil]; // tell it you are going to remove the child
[oldViewController.view removeFromSuperview]; // remove view
[oldViewController removeFromParentViewController]; // tell it you have removed child; this calls `didMoveToParentViewController` for you
newViewController.view.frame = self.view.bounds; // use `bounds`, not `frame`
newViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // be explicit regarding resizing mask if setting `frame`
[self addChildViewController:newViewController]; // tell it that you are going to add a child; this calls `willMoveToParentViewController` for you
[self.view addSubview:newViewController.view]; // add the view
[newViewController didMoveToParentViewController:self]; // tell it that you are done
}
Обратите внимание на последовательность звонков, в которой важен порядок, в котором вы их называете. При добавлении, например, вы звоните,
addSubview
, и в таком порядке. Обратите внимание, как для
didMoveToParentViewController
говорит:
Если вы реализуете свой собственный контроллер представления контейнера, он должен вызывать метод контроллера дочернего представления после завершения перехода к новому контроллеру или, если перехода нет, сразу после вызова метода.
И, если вам интересно, почему мы не звоним и в этом случае, это потому, что, как говорится ,
addChildViewController
делает это для вас:
Когда ваш пользовательский контейнер вызывает
addChildViewController:
, он автоматически вызывает метод контроллера представления, который будет добавлен в качестве дочернего перед его добавлением.
Точно так же при удалении вы звоните,
removeFromSuperview
, и . Как говорится в документациив документации :
Если вы реализуете свой собственный контроллер представления контейнера, он должен вызывать
willMoveToParentViewController:
метод дочернего контроллера представления перед вызовом метода, передав родительское значениеnil
.
И, опять же, если вам интересно, почему мы не вызываем при удалении дочернего элемента, потому что, как говорится документацияв документации , это делается за вас:
Метод автоматически вызывает
didMoveToParentViewController:
метод дочернего контроллера представления после того, как он удаляет дочерний элемент.
К вашему сведению, если вы анимируете удаление подпредставления, поместите вызов в
removeFromParentViewController
в обработчике завершения анимации.
Но если вы выполните правильную последовательность вызовов сдерживания, описанную выше, тогда ребенок получит все соответствующие события, связанные с представлением.
Для получения дополнительной информации (в частности, почему эти
willMoveToParentViewController
и
didMoveToParentViewController
звонки так важны), см. видео WWDC 2011 « Реализация ограничения UIViewController» . Также см. Раздел « Реализация контроллера представления контейнера »
UIViewController
документация .
В качестве небольшого замечания убедитесь, что при добавлении дочернего представления в качестве подпредставления ссылайтесь на представление родительского контроллера представления, а не на. В
frame
родительского представления находится в системе координат своего супервизора . Он находится в своей собственной системе координат.
Вы можете не заметить разницы, когда родительский вид занимает весь экран, но как только вы примените это в сценарии, когда родительский вид не занимает весь экран, вы начнете сталкиваться с рассогласованием кадров. Всегда используйте
bounds
при настройке координат для детей. (Или используйте ограничения, которые полностью избавят вас от этой глупости.)
Возможно, излишне говорить, что если вы хотите просто добавить дочерний элемент при создании экземпляра родительского объекта, можно полностью реализовать вложение контроллера представления в раскадровках без каких-либо из них.
add
/
remove
и
willMove
/
didMove
звонит вообще. Просто используйте «Представление контейнера» и передайте все данные, которые потребуются ребенку во время инициализации, используя
prepareForSegue
.
Например, если у родителя было свойство с именем
bar
и вы хотели обновить свойство с именем
baz
у ребенка:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[ChildViewController class]]) {
ChildViewController *destination = segue.destinationViewController;
destination.baz = self.bar;
}
}
Теперь, если вы хотите программно добавлять / удалять детей, используйте, как описано выше. Но раскадровка «Container View» может обрабатывать все вызовы включения представления для простых сценариев с очень небольшим кодом.