Xcode: напевно, найкращий спосіб роботи з сторибордами


Цей пост є вільним перекладом статті Xcode: A Better Way to Deal with Storyboards by Stan Ostrovskiy
Деякі приклади коду в оригінальній статті застаріли (зважаючи виходу Swift 3) і в перекладі були змінені.
Поради та рекомендації по роботі з Interface Builder.
Apple серйозно поліпшили Interface Builder в новому Xcode 8. Використання size classes стало більш інтуїтивним, можливість масштабування сториборда — дуже зручною, а повне прев'ю прям в Interface Builder — просто чудовим. Для тих у кого були сумніви щодо використання Interface Builder, це може стати хорошими плюсами.
З іншого боку, у багатьох розробників все ще є деякі проблеми з Interface Builder коли вони створюють великі багатоекранні програми зі складною навігацією.
У цій статті я поділюся деякими з кращих практик для роботи з сторибордами у вашому проекті. Ви вже користуєтеся Interface Builder, чи тільки робите перші кроки в цьому напрямку? — у будь-якому випадку, ці поради будуть корисні для вас.
1. Якщо ви працюєте в команді, використовуйте окремий сториборд для кожного екрану. Навіть якщо ви працюєте один — це, напевно, стане хорошою звичкою.
У вашому проекті є один файл main.storyboard, який виглядає ось так?

З точки зору дизайнера, все добре: повністю видно UI і навігацію. І це саме те, для чого Interface Builder і був створений.
Але для розробника це несе безліч проблем:
  • Контроль версій: конфлікти злиття сторибордов дуже важко вирішувати, так що робота в окремих сторибордах зробить життя вашої команди простіше.
  • Файл сториборда стає об'ємним і в ньому складно орієнтуватися. Як часто ви випадково змінювали constraint кліком мишки не в тому в'ю-контролері?
  • Вам необхідно присвоювати кожному в'ю-контролеру свій storyboard ID і це може призвести до помилок: вам треба «хардкодить» цей ID кожен раз коли хочете використовувати цей в'ю-контролер в коді.
Як же пов'язати різні сторіборди у вашому проекті? Є два способи.
  1. Використовуйте посилання на сторіборди (storyboard referencing), які з'явилися в Xcode 7.
  2. Пов'язуйте сторіборди безпосередньо в коді.
Про першому способі ви можете почитати детальніше тут.
Я розповім про другому способі, так як він широко використовується для складних проектів.
2. Використовуйте одні і ті ж імена для файлу з сторибордом і для пов'язаного класу контролера (спадкоємця UIViewController).
Це спростить правила іменування, а також дасть деякі "плюшки" про які поговоримо в пункті 3.
3. Ініціалізувати сториборд безпосередньо в класі контролера.
Коли справа доходить до ініціалізації в'ю-контролера через сториборд, я часто бачу наступний код:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController")

Трохи "бруднувато": вам потрібно назвати сториборд, вам потрібен storyboard ID в'ю-контролера, і вам необхідно використовувати цей патерн кожен раз, коли ви створюєте HomeViewController.
Краще перенести цей код у сам клас контролера і використовувати статичний метод, щоб ініціалізувати контролер з допомогою сториборда:
class HomeViewController: UIViewController { 
static func storyboardInstance() -> HomeViewController? { 
let storyboard = UIStoryboard(name: "HomeViewController", bundle: nil)
return storyboard.instantiateInitialViewController() as? HomeViewController 
}
}

Якщо ви прислухаєтеся попереднього раді (однакові імена файлів), то можете уникнути "харкода" імені сториборда і скористатися String(describing:):
let storyboard = UIStoryboard(name: String(describing: self), bundle: nil)

Переконайтеся, що файл сториборда таке ж ім'я, як і у класу контролера. Інакше ваш додаток буде «крэшится» коли ви спробуєте створити посилання на такий сториборд.
Це робить код більш читабельним і резервний:
class HomeViewController: UIViewController {
static func storyboardInstance() -> HomeViewController? { 
let storyboard = UIStoryboard(name: String(describing: self), bundle: nil)
return storyboard.instantiateInitialViewController() as? HomeViewController 
}
}

Якщо ви хочете мати доступ до в'ю-контролеру через instantiateInitialViewController() переконайтеся, що ви вказали цей в'ю-контролер як initialViewController у Interface Builder. Якщо у вас кілька в'ю-контролерів на одному сториборде, вам доведеться використовувати instantiateViewController(withIdentifier: _ )
Тепер, ініціалізація такого в'ю-контролера займе один рядок:
let homeViewController = HomeViewController.storyboardInstance()

Просто і зрозуміло, чи не так?
Ви можете використовувати цей підхід для ініціалізації в'ю з nib:
class LoginView: UIView {
static func nibInstance() -> LoginView? {
let nib = Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)
return nib?.first as? LoginView
}
}

4. Не перевантажуйте свій проект переходами на сториборде.
У вас не буде переходів, якщо ви скористаєтесь порадою з пункту 1. Але навіть якщо у вас є кілька в'ю-контролерів в одному сториборде, використання переходів (segues) для навігації між ними — не дуже хороша ідея:
  • Вам треба дати ім'я кожного переходу (segue), що саме по собі може призвести до помилок. «Хардкодить» рядки з іменами — погана практика.
  • Метод prepareForSegue буде просто його не можна прочитати, коли ви будете працювати з декількома segue, використовуючи оператори розгалуження if/else або switch.
Яка альтернатива? Коли ви хочете перейти до наступного в'ю-контролеру по натисненню на кнопку, просто додайте IBAction для цієї кнопки і ініціалізувати в'ю-контролер в коді: адже це всього один рядок, як ви пам'ятаєте з пункту 3.
@IBAction func didTapHomeButton(_ sender: AnyObject) {
if let nextViewController = NextViewController.storyboardInstance() {
// initialize all your class properties
// nextViewController.property1 = ... 
// nextViewController.property2 = ... 

// either push or the present nextViewController,
// depending on your navigation structure 
// present(nextViewController, animated: true, completion: nil) 

// or push 
navigationController?.pushViewController(nextViewController, animated: true)
}
}

5. Unwind segue? Не, не чув.
Іноді навігація передбачає повернення до попереднього екрану.
Дуже поширена помилка: використовувати новий для навігації перехід до попереднього в'ю-контролеру. Такий перехід створює новий екземпляр в'ю-контролера, який вже знаходиться в стеці, замість того, щоб прибрати поточний в'ю-контролер і таким чином повернутися до попереднього.
Починаючи з iOS 7, Interface Builder дає вам можливість зробити "unwind" навігаційного стека.

Unwind segue дозволяє вам вказати повернення на попередній екран. Це звучить досить просто, але на практиці це вимагає деяких додаткових дій та тільки збиває з пантелику розробника:
  • Зазвичай, коли ви створюєте дію для кнопки (action), Interface Builder згенерує для вас код (IBAction). У цьому ж випадку, очікується, що код вже написано до того, як ви затиснете «Ctrl» і перетягніть дію від вашої кнопки «Exit».
  • Зазвичай коли ви створюєте дію для кнопки, код цієї дії складається в тому ж класі, якому і належить кнопка. Для Unwind Segues, вам потрібно писати код у класі того в'ю-контролера, який цей перехід відбудеться.
  • Метод prepareForUnwind буде мати всі ті ж недоліки, що і метод prepareForSegue (див. попередній пункт).
Який же більш простий спосіб?
Простіше робити це в коді: замість створення дії "unwind" для вашої кнопки, створіть звичайний IBAction і використовуйте dismissViewController або popViewController (в залежності від вашої навігації):
@IBAction func didTapBackButton(_ sender: AnyObject) { 
// if you use navigation controller, just pop ViewController:
if let nvc = navigationController { 
nvc.popViewController(animated: true)
} else {
// otherwise, dismiss it
dismiss(animated: true, completion: nil)
}
}

На сьогодні це все. Я сподіваюся, ви знайдете щось корисне для себе.
Від перекладача:
Завдяки методу описаного в цій статті, я дуже сильно спростив роботу зі сторибордами у своєму поточному проекті. Поки я працював над ним один — все було чудово, але як тільки з'явилися інші розробники — робота з сторибордом перетворилася на справжнє пекло. Від відчаю ми практично перейшли до "банановому методу" (можна почитати тут у розділі "Pass the banana").
Звичайно ж, в ідеалі потрібно буде рано чи пізно прийти до VIPER. Але про це буде вже інший пост. :)
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.