Child View Controller
Так же, как UIView может быть добавлен в другой UIView для формирования иерархии, view контроллер может стать child контроллером другого view контроллера. Это позволяет составлять UI из нескольких блоков, что в итоге снижает количество вызовов view контроллера, это упрощает его повторное использование.
При добавлении в качестве child элемента, view контроллер автоматически подстраивается под размер окна приложения - но, как и в случае с сабвью автономного UIView, view child view контроллера можно масштабировать и перемещать с использованием фреймов и констрейнтов Auto Layout.
Чтобы добавить view контроллер как child элемент, мы используем следующие три вызова API:
let parent = UIViewController()
let child = UIViewController()
// First, add the view of the child to the view of the parent
parent.view.addSubview(child.view)
// Then, add the child to the parent
parent.addChild(child)
// Finally, notify the child that it was moved to a parent
child.didMove(toParent: parent)
И чтобы удалить child элемент, добавленный к родителю, мы используем следующие три вызова:
// First, notify the child that it’s about to be removed
child.willMove(toParent: nil)
// Then, remove the child from its parent
child.removeFromParent()
// Finally, remove the child’s view from the parent’s
child.view.removeFromSuperview()
Как вы можете видеть выше, обе эти операции требуют довольно большого количества шагов - поэтому, если мы начнем широко использовать child view контроллеры в нашем проекте, он может стать перегруженным большим количеством быстро выполняющихся шагов.
Одним из решений этой проблемы является добавление в UIViewController расширения, способного объединять шаги, необходимые для добавления или удаления child view контроллера, в два простых метода, например:
extension UIViewController {
func add(_ child: UIViewController) {
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
func remove() {
// Just to be safe, we check that this view controller
// is actually added to a parent before removing it.
guard parent != nil else {
return
}
willMove(toParent: nil)
view.removeFromSuperview()
removeFromParent()
}
}
Child view контроллеры могут быть особенно полезны для добавления функциональности в пользовательский интерфейс, используемый в нашем проекте. Например, мы хотим наглядно отобразить загрузку содержимого для каждого экрана - это просто реализовать с помощью child view контроллера, который может быть легко добавлен при необходимости в будущем.
Для этого, во-первых, давайте создадим LoadingViewController, который отображает индикатор загрузки в центре своего интерфейса. Это можно реализовать так:
class LoadingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let spinner = UIActivityIndicatorView(style: .gray)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.startAnimating()
view.addSubview(spinner)
// Center our spinner both horizontally & vertically
NSLayoutConstraint.activate([
spinner.centerXAnchor.constraint(equalTo: view.centerXAnchor),
spinner.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
Затем, когда мы начинаем загрузку содержимого в одном из наших view контроллеров, мы можем просто добавить наш новый LoadingViewController в качестве child элемента для отображения индикатора загрузки, а затем удалить его, как только загрузка закончится:
class ContentViewController: UIViewController {
private let loader = ContentLoader()
func loadContent() {
let loadingVC = LoadingViewController()
add(loadingVC)
loader.load { [weak self] content in
loadingVC.remove()
self?.render(content)
}
}
}
Довольно круто! 👍 Но вопрос в том, зачем проходить через все проблемы реализации view контроллера для чего-то вроде индикатора загрузки, вместо простого использования простого UIView? Вот несколько причин:
- View контроллер получает доступ к событиям, таким как viewDidLoad и viewWillAppear, даже когда они используются в качестве child элементов, что может быть действительно полезно для многих видов пользовательского интерфейса.
- View контроллер более автономен и может как включать в себя логику, необходимую для управления пользовательским интерфейсом, так и самостоятельно формировать этот пользовательский интерфейс.
- При добавлении в качестве child элемента view контроллер автоматически заполняет экран, сводя к минимуму необходимость в дополнительной разметки для полноэкранных интерфейсов.
- Когда часть пользовательского интерфейса реализована в виде view контроллера, его можно использовать во многих различных контекстах, включая как передачу навигации на контроллер, так и вставку в качестве child элемента.
Конечно, это не означает, что все пользовательские интерфейсы должны быть реализованы с использованием child view контроллеров, но child view контроллеры являются хорошим инструментом, о котором следует помнить для создания пользовательских интерфейсов в более модульном виде.
Спасибо за прочтение! 🚀