Делегаты в Swift на простом примере
Эта статья для уровня 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 с финальным проектом - ссылка.
Спасибо, что дочитали :)