Источник: Nuances of Programming
День настал
Я недавно работал над iOS приложением, которое уже находится на рынке. Оно было выпущено прежде, чем Apple запустили новый чудо фреймворк SwiftUI, и разрабатывалось с помощью одной раскадровки, реализующей весь UI.
Обычно сперва приложение задумывается, конструируется и создается в одном виде, а затем уже постепенно вносятся правки, улучшения и изменения в потоках.
То есть к оригиналу время от времени добавлялись новые сцены и в итоге их количество стало огромным. Теперь Xcode открывает эту одну огромную раскадровку с усилием и тормозами.
Причем сложным стало не только перемещение между сценами, но и проверка их связей, выбор контроллера представления для редактирования или устранения дефекта графики.
Итак, настал тот самый день, когда моей задачей стало разделить сцены этого огромного объекта, содержащего весь UI, на множество раскадровок.
Целью было повысить управляемость, ускорить их открытие в Xcode, а также на случай увеличения числа разработчиков в проекте, уменьшить число проблем, связанных со слиянием и конфликтами версий.
Множество раскадровок
Я уже работал над рядом проектов, использовавших несколько раскадровок, но при этом они все были задуманы и спроектированы в таком виде с самого начала, поэтому все было достаточно просто.
Я знал взаимное подключение контроллеров отображения, представленных в различных сценах. Переключение же между сценами, размещенными в разных раскадровках, было организовано следующим образом:
let storyboard = UIStoryboard(name: “SecondStoryboard”, bundle: nil)let secondVC = storyboard.instantiateViewController(identifier:”SecondViewController”)show(secondVC, sender: self)
Переключения между сценами в одной раскадровке управлялись переходами, настроенными в конструкторе интерфейса. Мне пришлось столкнуться с новыми проблемами:
- Группировкой существующих сцен.
- Разделением групп на отдельные раскадровки.
- Проверкой работоспособности навигации между сценами из разных раскадровок.
Группировка контроллеров отображения была простейшей задачей, и она была отчасти сделана. Контроллеры, отвечавшие за логин, пароль, восстановление и прочее, были изначально определены вместе в раскадровке, поэтому все было достаточно просто. Для распределения сцен по раскадровкам и дальнейшей навигации между ними мне пришлось обратиться к помощи в интернете.
Там я и отыскал возможности, о которых ранее не слышал, и взял на вооружение два подхода, каждый из которых оказался по-своему интересен.
Целостное построение интерфейса
Слышали ли вы когда-нибудь о связках раскадровок? Компания Apple ввела их в iOS 9 и macOS 10.11. Они-то как раз и решают мою задачу.
С их помощью можно разбить большую раскадровку на множество меньших. Такая связка объединяет множество раскадровок в одну комплексную. Меня поразило, насколько легко использовать эту возможность. Для этого нужно:
- Открыть большую раскадровку.
- Выбрать сцены для извлечения и помещения в отдельный файл.
- Использовать опцию “рефакторинг в раскадровку…”, находящуюся в меню редактора Xcode.
Наша любимая IDE только спросит имя новой раскадровки, а мы в завершении просто выберем поместить новый файл в новую папку или в существующую. Сделано!
Навигация между сценами с помощью уже имеющихся переходов продолжает работать, не требуя изменений. Функции “Подготовить”, используемые для настройки контроллеров представления для, например, передачи данных целевой сцене, также продолжают работать.
Ручной программный подход
Этот подход подразумевает самостоятельное разделение раскадровки на части.
В моем конкретном случае после определения основных групп сцен, я продублировал оригинальную раскадровку, создав идентичную копию файлов.
Далее я присвоил соответствующие имена каждому дубликату (“Логин”, “Содержимое”, “Настройки” и пр.).
Я импортировал эти новые раскадровки в решение, а затем поочередно очистил каждую от ненужных сцен, оставив лишь ей соответствующие. При таком подходе дополнительно приходится писать код для правильного вызова контроллеров отображения, установленных в различных раскадровках. Он уже упоминался в начале статьи:
let storyboard = UIStoryboard(name: "SecondStoryboard", bundle: nil)let secondVC = storyboard.instantiateViewController(identifier: "SecondViewController")show(secondVC, sender: self)
Его легко писать и читать, поэтому его можно оперативно заменить на следующий:
self.performSegue(withIdentifier: "goThere", sender: self)
В завершении, если целевая сцена требует значения из вызывающего контроллера отображения, то код в подготовительной функции больше не нужен:
С другой стороны, будет удобнее, если нам не придется явно инстанцировать раскадровку и контроллер отображения каждый раз, когда надо переключиться между сценами.
Компонент маршрутизации (Routable)
Этот компонент помогает легко отображать сцену из раскадровки, что хорошо видно по трем методам в следующем примере класса ViewController:
Рефакторинг большой раскадровки в несколько меньших
День настал
Я недавно работал над iOS приложением, которое уже находится на рынке. Оно было выпущено прежде, чем Apple запустили новый чудо фреймворк SwiftUI, и разрабатывалось с помощью одной раскадровки, реализующей весь UI.
Обычно сперва приложение задумывается, конструируется и создается в одном виде, а затем уже постепенно вносятся правки, улучшения и изменения в потоках.
То есть к оригиналу время от времени добавлялись новые сцены и в итоге их количество стало огромным. Теперь Xcode открывает эту одну огромную раскадровку с усилием и тормозами.
Причем сложным стало не только перемещение между сценами, но и проверка их связей, выбор контроллера представления для редактирования или устранения дефекта графики.
Итак, настал тот самый день, когда моей задачей стало разделить сцены этого огромного объекта, содержащего весь UI, на множество раскадровок.
Целью было повысить управляемость, ускорить их открытие в Xcode, а также на случай увеличения числа разработчиков в проекте, уменьшить число проблем, связанных со слиянием и конфликтами версий.
Множество раскадровок
Я уже работал над рядом проектов, использовавших несколько раскадровок, но при этом они все были задуманы и спроектированы в таком виде с самого начала, поэтому все было достаточно просто.
Я знал взаимное подключение контроллеров отображения, представленных в различных сценах. Переключение же между сценами, размещенными в разных раскадровках, было организовано следующим образом:
let storyboard = UIStoryboard(name: “SecondStoryboard”, bundle: nil)let secondVC = storyboard.instantiateViewController(identifier:”SecondViewController”)show(secondVC, sender: self)
Переключения между сценами в одной раскадровке управлялись переходами, настроенными в конструкторе интерфейса. Мне пришлось столкнуться с новыми проблемами:
- Группировкой существующих сцен.
- Разделением групп на отдельные раскадровки.
- Проверкой работоспособности навигации между сценами из разных раскадровок.
Группировка контроллеров отображения была простейшей задачей, и она была отчасти сделана. Контроллеры, отвечавшие за логин, пароль, восстановление и прочее, были изначально определены вместе в раскадровке, поэтому все было достаточно просто. Для распределения сцен по раскадровкам и дальнейшей навигации между ними мне пришлось обратиться к помощи в интернете.
Там я и отыскал возможности, о которых ранее не слышал, и взял на вооружение два подхода, каждый из которых оказался по-своему интересен.
Целостное построение интерфейса
Слышали ли вы когда-нибудь о связках раскадровок? Компания Apple ввела их в iOS 9 и macOS 10.11. Они-то как раз и решают мою задачу.
С их помощью можно разбить большую раскадровку на множество меньших. Такая связка объединяет множество раскадровок в одну комплексную. Меня поразило, насколько легко использовать эту возможность. Для этого нужно:
- Открыть большую раскадровку.
- Выбрать сцены для извлечения и помещения в отдельный файл.
- Использовать опцию “рефакторинг в раскадровку…”, находящуюся в меню редактора Xcode.
Наша любимая IDE только спросит имя новой раскадровки, а мы в завершении просто выберем поместить новый файл в новую папку или в существующую. Сделано!
Навигация между сценами с помощью уже имеющихся переходов продолжает работать, не требуя изменений. Функции “Подготовить”, используемые для настройки контроллеров представления для, например, передачи данных целевой сцене, также продолжают работать.
Ручной программный подход
Этот подход подразумевает самостоятельное разделение раскадровки на части.
В моем конкретном случае после определения основных групп сцен, я продублировал оригинальную раскадровку, создав идентичную копию файлов.
Далее я присвоил соответствующие имена каждому дубликату (“Логин”, “Содержимое”, “Настройки” и пр.).
Я импортировал эти новые раскадровки в решение, а затем поочередно очистил каждую от ненужных сцен, оставив лишь ей соответствующие. При таком подходе дополнительно приходится писать код для правильного вызова контроллеров отображения, установленных в различных раскадровках. Он уже упоминался в начале статьи:
let storyboard = UIStoryboard(name: "SecondStoryboard", bundle: nil)let secondVC = storyboard.instantiateViewController(identifier: "SecondViewController")show(secondVC, sender: self)
Его легко писать и читать, поэтому его можно оперативно заменить на следующий:
self.performSegue(withIdentifier: "goThere", sender: self)
В завершении, если целевая сцена требует значения из вызывающего контроллера отображения, то код в подготовительной функции больше не нужен:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.identifier {
case "goThere":
let destination = segue.destination as! SecondViewController
destination.data = someData
default:
break
}
}
И может быть заменен на:
let storyboard = UIStoryboard(name: "SecondStoryboard", bundle: nil)
let secondVC = storyboard.instantiateViewController(identifier: "SecondViewControllerID")
secondVC.data = someData
show(secondVC, sender: self)
С другой стороны, будет удобнее, если нам не придется явно инстанцировать раскадровку и контроллер отображения каждый раз, когда надо переключиться между сценами.
Компонент маршрутизации (Routable)
Этот компонент помогает легко отображать сцену из раскадровки, что хорошо видно по трем методам в следующем примере класса ViewController:
import UIKit
class MyViewController: ViewController {
// ПОМЕТКА: - Публичные методы
public func goToPrivacyVC() {
self.show(storyboard: .settings, identifier:
"privacyViewControllerId")
}
public func goToLoginVC() {
self.present(
storyboard: .login,
identifier: "loginNavigationController",
modalPresentationStyle: .fullScreen)
}
public func goToFilesVC() {
self.show(storyboard: .media_and_Files, identifier: "Files",
configure: { controller in
// Передать данные в место назначения
if let vc = controller as? FilesViewController {
vc.myData = "ciao"
}
})
}
}
extension MyViewController: Routable {
enum StoryboardId: String {
case start_main = "Home_Main_Screens"
case login = "Login"
case settings = "Settings_Permissions_Agreement"
case media_and_Files = "Media"
}
}
Этот пример показывает, что не обязательно явно инстанцировать целевую раскадровку и контроллер отображения при каждом переносе пользователя на конкретный экран.
Отправка информации из источника и целевых контроллеров производится весьма просто. В конце статьи приведен полный код этого компонента routable.
Последние обновления Xcode 11 и iOS 13
К моему удивлению. я обнаружил, что Xcode 11 и iOS 13 представили некоторые новые возможности раскадровок. Я имею в виду SegueActions и специальные инициализаторы.
SegueAction
Мы уже заметили, что при использовании переходов для навигации между сценами нам нужно реализовать функцию prepare, перемещающую данные от стартового контроллера к остальным.
Нужно написать инструкцию переключения для проверки имени идентификатора перехода, а затем подготовить значения, которые мы хотим отправить целевому контроллеру. Но прежде нам необходимо проверить является ли идентификатор нулевым и в завершении выйти из функции.
Мы можем избежать реализации этой функции, использовав SegueAction — метод контроллера отображения, который UIKit вызывает о время перехода. Пример ниже демонстрирует простой вариант с SegueAction:
Целевой контроллер отображения должен быть реализован с пользовательским инициализатором, как показано в следующем примере:
Процедура формирования SegueAction очень проста. Как и всегда в IB, мы создаем переход. В моем примере я соединяю синюю кнопку с целевым контроллером отображения:
Мы выбираем вид перехода:
Затем создаем SegueAction, как если бы создавали IBAction, устанавливая связь Ctrl-перетаскиванием перехода в код viewController:
Именуем новую функцию:
Вот и создано действие SegueAction.
В моем примере я добавил в конце параметр myData для его передачи целевому ViewController:
Подготовительная функция больше не требуется.
Пользовательские инициализаторы
Пользовательские инициализаторы, созданные в примере выше, можно использовать другим образом. Я уже упомянул, что перемещение между ViewControllers может осуществляться без помощи переходов.
Например, мы можем создать обычный IBAction:
Версия instantiatateViewController, представленная в iOS 13, позволяет создавать блок, который вызывает пользовательский инициализатор:
Заключение
Нам еще долго придется иметь дело с раскадровками. У многих из нас есть приложения на рынке, которые потребуют поддержки и развития. Многие разработчики по-прежнему будут продолжать противиться внедрению SwiftUI для разработки новых проектов, поскольку считают, что эта технология еще недостаточно развита. У других разработчиков останутся проекты, начатые до появления SwiftUI.
Следовательно нам по-прежнему предстоит иметь дело с полезным инструментом. Когда мы вынуждены делить большую раскадровку на меньшие для облегчения ее поддержки и управления, можно прибегнуть к двум удобным решениям — связке раскадровок или ручному программному подходу, которые я и описал.
Хорошо, что разработчики Apple продолжают добавлять новый функционал в раскадровки. Это показывает их осведомленность о наличии на рынке множества приложений с UI, созданным при помощи этого инструмента, которые будут требовать поддержания и развития в течение как минимум нескольких лет.
Полный код для компонента маршрутизации (Routable)
Компонент маршрутизации и его расширение раскадровки были написаны Освальдом Пирелло, CEO и основателем OverApp. Выражаю ему свою благодарность.
Читайте также:
Читайте нас в телеграмме и vk
Перевод статьи Davide Fin: Refactoring From a Huge Storyboard to Multiple Smaller and More Manageable Objects