Эта статья для уровня trainee, а значит для совсем начинающих великолепных разработчиков
Основная цель статьи - рассказать просто, на примере, как можно использовать паттерн делегирования в Swift
.
Статья состоит из двух частей. Первая - удалим из проекта Storyboard
и напишем кодом простой интерфейс. Вторая - разберем, как с помощью делегата передать данные на предыдущий контроллер.
Часть 1
Создаем новый проект, назовем его DelegatePattern:

Размещать элементы будем кодом, поэтому удаляем Storyboard
из проекта:


Выбираем проект (стрелка 1), вкладка General
и в разделе Deployment Info
выделяем и удаляем Main
(стрелка 2)

Переходим в файл info.plist
(стрелка 1) и удаляем строку Application Scene Manifest

У нас не будет поддержки IPad
- файл SceneDelegate
можно тоже удалить:

Так как мы удалили Storyboard
, в файле AppDelegate
объявим свойство window
, а в методе application didFinishLaunchingWithOptions
, нужно добавить код ниже, чтобы указать стартовый контороллер. Остальные методы удалим, в нашем проекте они использоваться не будут.
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let rootVC = ViewController()
window?.rootViewController = rootVC
window?.makeKeyAndVisible()
return true
}
Теперь AppDelegate
выглядит вот так:

Переходим во ViewController
, в методе viewDidLoad
добавляем фиолетовый цвет для бэкграунда view
контроллера:

Запускаем проект, и если все сделали верно, запустится симулятор с фиолетовым контроллером:

Отлично, теперь мы можем продолжать писать интерфейс в коде. Для начала создадим еще один ViewController
. Нажимаем Command + N
и выбираем Cocoa Touch Class
:

Назовем его SecondViewController:

Поработаем с ViewController
, добавим на него кнопку перехода на SecondViewController:

Объявим новый метод makeConstraints
, в нем добавим кнопку на view
и создадим констрейнты.
private func makeConstraints() {
view.addSubview(toSecondViewControllerButton) // добавляем кнопку на view
NSLayoutConstraint.activate([
toSecondViewControllerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), // центр по оси Х
toSecondViewControllerButton.centerYAnchor.constraint(equalTo: view.centerYAnchor) // центр по оси Y
])
}
И обязательно, нужно установить значение свойстваbutton.translatesAutoresizingMaskIntoConstraints = false
в клоужере создания кнопки. Вот как теперь выглядит ViewController
:

Проверим симулятор:

Теперь аналогично добавим UILabel, в нем в последующем и будем менять текст при возвращении из следующего контроллера.
Можете сами потренироваться и создать лейбл. Если возникнут проблемы, подглядите здесь :)
Первым делом добавим сам элемент UILabel:

Добавляем лейбл на view
и, создадаем констрейнты, расширив метод makeConstraints
:
private func makeConstraints() {
view.addSubview(toSecondViewControllerButton)
view.addSubview(someLabel) // добавляем на экран
NSLayoutConstraint.activate([
toSecondViewControllerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
toSecondViewControllerButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
someLabel.centerXAnchor.constraint(equalTo: toSecondViewControllerButton.centerXAnchor), // центр по оси X
someLabel.centerYAnchor.constraint(equalTo: toSecondViewControllerButton.centerYAnchor, constant: 40) // центр по оси Y
])
}
Отлично, теперь на view
есть кнопка и лейбл. Симулятор выглядят так:

Чтобы кнопка заработала и при ее нажатии открывался следующий экран, добавим ей свойство button.addTarget():

Объявим методtoSecondVCButtonPressed()
, который будет отрабатывать по нажатию на кнопку:

Перейдем в SecondViewController
и в методе viewDidLoad()
добавим цвет бэкграунда view
второго контроллера:

Проверяем, что получилось, собираем проект. Если все сделано верно, по нажатию на кнопку Go to second VC
, откроется второй контроллер с серым фоном.

Часть 2
Пора приступать к передаче данных при закрытии "серого" контроллера. Как вы видели в названии статьи, будем использовать делегат :)
Объявим протокол SecondViewControllerDelegate
в файле класса ViewController
и обязательно укажем тип AnyObject
. Это нужно для того, чтобы протокол работал с классами, а это, в свою очередь, позволит создавать слабые ссылки и избежать retain cycle между контроллерами. Наш протокол будет содержать только один метод - для замены текста в лейбле ViewController
'a.

Реализовывать метод протокола будет ViewController
, подпишем его под протокол в extension
'е и напишем логику для метода, которая будет менять текст:

В аргумент text
, находящийся на 69 строке, придет новый текст с другого контроллера, а на 70 строке мы заменим стандартный текст лейбла.
Если навести курсор на аргумент text
, Xcode
подсветит какой text
к какому относится.

Переходим в SecondViewController
. Помните мы создали протокол и объявили его anyObject
? Теперь пора создать слабую ссылку, которая будет иметь тип делегата, она будет жить в SecondViewController
и через нее мы сможем добраться до методов делегата.

Когда мы закрываем SecondViewController
, смахивая его вниз, срабатывает метод deinit
. В нашем примере это отличное место, чтобы передать новый текст в лейбл ViewController
'a. Добираемся через переменную delegate
до метода newTextForLabel
и передаем в него новый текст для лейбла на первом контроллере "New text".
(*PS: метод deinit() в статье используется только для примера, поскольку мы точно уверены в том, что контроллер выгрузится из памяти.)

Как пример, если создать кнопку закрытия второго экрана, тогда self.delegate?.newTextForLabel(text: "New text")
поселился бы в методе, срабатывающем по нажатию на кнопку закрытия.
Все почти готово, осталось дело за малой деталью, о которой лично я всегда забываю :) нужно сообщить нашему SecondViewController
, кто будет его делегатом (то есть кто будет что-то делать с теми данными которые, он отправил).
Возвращаемся в ViewController,
и там, где мы создавали для кнопки метод перехода на SecondViewController
подпишемся под делегата:

Собираем проект и проверяем:

PS: Почему текст обновляется с задержкой? Дело в том, что deinit
выгружает контроллер из памяти, поэтому проходит какое-то количество времени, пока контроллер выгрузится и текст сменится. Если вы будете использовать делегат, например, в методе UIViewController
'a - dismiss()
или в вашем методе кнопки, то никаких задержек не будет.
GitHub с финальным проектом - ссылка.
Спасибо, что дочитали :)