У вас большой UIViewController? У многих да. С одной стороны, в нём работа с данными, с другой — с интерфейсом.
Задачи отделения логики от интерфейса описаны в сотнях статей про архитектуру: MVP, MVVM, VIPER. Они решают проблему потока данных, но не отвечают на вопрос как работать с интерфейсом: в одном месте остается создание элементов, лейаут, настройка, обработка ввода и анимации.
Давайте отделим view от controller и посмотрим чем нам поможет loadView().

Интерфейс приложения для iOS — это иерархия
Если посмотреть на методы класса
Жизненный цикл
Мы можем переопределить метод и указать свой класс.
Контроллер загрузит
Но пока компилятор не знает о классе и считает, что там обычный
Теперь можно видеть переменные
Шрифты, цвета, констрейнты и иерархию можно настроить прямо в конструкторе CustomView:
Лучшее место для ручного лейаута — метод
Если есть время, то я делаю
Преимущество в инкапсуляции: внутренняя логика скрыты за интерфейсом. Например, валидность объекта может показываться цветом области, а не квадрата, но контроллер ничего об этом не узнает.
Если использовать Interface Builder, то часто
Подкласс для
Нужно выделить объект

Если выбрать контрол внутри

Если создавать интерфейс в коде, то все объекты доступны после
На мой вкус, контроллеру стоит оставлять все действия пользователя. Из стандартных:
При этом
В Objective-C можно полноценно заменить тип
В примере на GitHub можно посмотреть на разделение классов для простой задачи: цвет квадрата зависит от его положения (в зелёной области он зелёный, вне её — красный).
Чем сложнее экран, тем лучше эффект: контроллер уменьшается, код переносится на свои места. Код просто переносится во
Я использовал этот код в разных проектах и с разными людьми, каждый раз команда приветствовала упрощение и подхватывала практику. Надеюсь, что и вам она понравится. Всем лёгких
Задачи отделения логики от интерфейса описаны в сотнях статей про архитектуру: MVP, MVVM, VIPER. Они решают проблему потока данных, но не отвечают на вопрос как работать с интерфейсом: в одном месте остается создание элементов, лейаут, настройка, обработка ввода и анимации.
Давайте отделим view от controller и посмотрим чем нам поможет loadView().

Интерфейс приложения для iOS — это иерархия
UIView. Задачи каждой view: создать элементы, настроить, разложить по местам, анимировать. Это видно из методов, которые есть в классе UIView: addSubview(), drawRect(), layoutSubviews().Если посмотреть на методы класса
UIViewController, то видно, что он занимается управлением view: загружает, реагирует на загрузку экранов и действия пользователя, показывает новые экраны. Часто код, который должен быть в UIView, мы пишем в подклассах UIViewController, от этого он становится слишком большим. Отделим его.loadView()
Жизненный цикл
UIViewController начинается с loadView(). Упрощённая реализация выглядит так: // CustomViewController.swift func loadView() { self.view = UIView() }
Мы можем переопределить метод и указать свой класс.
super.loadView() вызывать не нужно!// CustomViewController.swift override func loadView() { self.view = CustomView() }
Реализация CustomView.swift
// CustomView.swift final class CustomView { let square: UIView = UIView() init() { super.init() square.backgroundColor = .red addSubview(square) } }
Контроллер загрузит
CustomView, добавит его в иерархию, выставит .frame. Свойство .view будет нужного нам класса: // CustomViewController.swift print(view) // CustomView
Но пока компилятор не знает о классе и считает, что там обычный
UIView. Поправим это функцией с приведением типа:// CustomViewController.swift func view() -> CustomView { return self.view as! CustomView }
Теперь можно видеть переменные
CustomView:// CustomViewController.swift func viewDidLoad() { super.viewDidLoad() view().square // Работает }
Упрощаем с помощью associatedtype
Руслан Кавецкий предложил убрать дублирование кода с помощью расширения протокола:
protocol ViewSpecificController { associatedtype RootView: UIView } extension ViewSpecificController where Self: UIViewController { func view() -> RootView { return self.view as! RootView } }
Для каждого нового контроллера нужно только указать протокол и подкласс для егоUIViewчерезtypealias:
// CustomViewController.swift final class CustomViewController: UIViewController, ViewSpecificController { typealias RootView = CustomView func viewDidLoad() { super.viewDidLoad() view().square // Работает } }
Код в подклассе UIView
Создание и настройка контролов
Шрифты, цвета, констрейнты и иерархию можно настроить прямо в конструкторе CustomView:
// CustomView.swift init() { super.init() backgroundColor = .lightGray addSubview(square) }
layoutSubviews()
Лучшее место для ручного лейаута — метод
layoutSubviews(). Он вызывается каждый раз, когда меняется размер view, поэтому можно опираться на размер bounds для правильных расчётов:// CustomView.swift override func layoutSubviews() { super.layoutSubviews() square.frame = CGRect(x: 0, y: 0: width: 200, height: 200) square.center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) }
Приватные контролы, публичные свойства
Если есть время, то я делаю
property контролов приватными, но управляю ими через публичные переменные или функции «в области знаний». Проще на примере:// CustomView.swift private let square = UIView() var squarePositionIsValid: Bool { didSet { square.backgroundColor = squarePositionIsValid? .green : .red } } func moveSquare(to newCenter: CGPoint) { square.center = newCenter }
Преимущество в инкапсуляции: внутренняя логика скрыты за интерфейсом. Например, валидность объекта может показываться цветом области, а не квадрата, но контроллер ничего об этом не узнает.
Что остаётся во viewDidLoad()?
Если использовать Interface Builder, то часто
viewDidLoad() пустой. Если view создавать в коде, то нужно привязать их действия через target-action паттерн, добав��ть UIGestureRecognizer или связать делегаты. Настраиваем через Interface Builder
Подкласс для
view можно настроить через Interface Builder (далее IB). Нужно выделить объект
view (не контроллер) и задать его класс. Писать собственный loadView() не нужно, контроллер сделает это сам. Но приводить тип UIView всё ещё приходится. 
IBOutlet в UIView
Если выбрать контрол внутри
view, то Assistant Editor распознает класс UIView и предложит его в качестве второго файла в Automatic режиме. Так можно переносить IBOutlet во view.
Если не работает
Открыть класс

CustomView вручную, написать IBOutlet. Теперь можно потащить за маркер и навести на элемент в IB.
Если создавать интерфейс в коде, то все объекты доступны после
init(), но при работе с IB доступ к IBOutlet появляется только после загрузки интерфейса из UIStoryboard в методе awakeFromNib():// CustomView.swift func awakeFromNib() { super.awakeFromNib() square.layer.cornerRadius = 8 }
IBAction в UIViewController
На мой вкус, контроллеру стоит оставлять все действия пользователя. Из стандартных:
- target-action от контролов
- реализация delegate в
UIViewController - реализация блоков
- реакция на
Notification
При этом
UIViewController управляет только интерфейсом. Всё что касается бизнес-логики стоит выносить из контроллера, но это уже на выбор: MVP, VIPER и т.д.Objective-C
В Objective-C можно полноценно заменить тип
UIView. Для этого нужно объявить property с нужным классом, переопределить setter и getter, указав класс:// CustomViewController.m @interface CustomViewController @property (nonatomic) CustomView *customView; @end @implementation - (void)setView:(CustomView *)view{ [super setView:view]; } - (CustomView *)view { return (CustomView *)super.view; } @end
Конец
В примере на GitHub можно посмотреть на разделение классов для простой задачи: цвет квадрата зависит от его положения (в зелёной области он зелёный, вне её — красный).
Чем сложнее экран, тем лучше эффект: контроллер уменьшается, код переносится на свои места. Код просто переносится во
view, но инкапсуляция упрощает взаимодействие �� чтение кода. Иногда view можно переиспользовать с другим контроллером. Например, разные контроллеры для iPhone и iPad по-своему реагируют на появление клавиатуры, но это никак не меняет код view.Я использовал этот код в разных проектах и с разными людьми, каждый раз команда приветствовала упрощение и подхватывала практику. Надеюсь, что и вам она понравится. Всем лёгких
UIViewController!