Ниже я хочу сравнить различные методологии с использованием Storyboard или набора Xib-фалов, с ручной верстой (с использованием autolayout и без). Не претендую на полноту раскрытия темы и буду рад, если вы укажете мне на ошибки и/или предложите другие методологии и критерии сравнения.
Пост не содержит экспериментов, академических вычислений и основывается на моих знаниях и опыте в области iOS разработки. Был бы рад узнать мнения экспертов!
С теми или иными оговорками, существуют следующие методологии разработки:
- Один Storyboard и все в нем (минимум кода верстки в коде) — используется в небольших проектах; очень неудобен при разработке командой разработчиков.
- Несколько Storyobard-ов и все в них.
- Забываем про Storybaord, все UI-контролы помещаем в xib-файлы (см. этот пост).
- Забываем про Storyboard и Xib, но используем autolayout — привет PureLayouts.
- Забываем про все Storyboard/Xib/AutoLayouts и настраиваем всем View фреймы в ручную.
- Забываем про UIKit — используем сторонние решения AsyncDisplayKit от Facebook или ComponentsKit. (Такого опыта у меня нет, поэтому ничего путного не скажу).
Storyboard и Xib
Проблемы:
- Код с анимациями туда не поместить.
- Если их несколько, то при переходе с одного на другой — создается нагрузка на main thread (NSKeyedArchiver должен распарсить сториборду, а он сам по себе медленный).
- Нельзя все сделать в Storybaord при все желании. Например, задать cornerRadius, shadowOffset и т.д.
Плюсы:
- User-story виден при просмотре.
- Мы абстрагируемся от типа перехода и для осуществления самого перехода достаточно знать segue identifier (см. RamblerSegues), также, при использовании автоматических dependency injection библиотек (см. typhoon), мы абстрагируемся от зависимостей.
Ручная верстка с использованием Autolayout-ов
Использовал различные тулы, наиболее, удобные — PureLayout и Cartography (для swift).
Проблемы:
- Больше кода (приблизительно в 1.2 раза).
- Ваши предложения?
Плюсы:
- Немного быстрей — экономим на парсинге Xib/Storyboard файла NSKyedArchiver-ом.
- Декларативно и все в одном месте (не нужно постоянно переключаться между Storyboard и текстом).
- Удобней делать анимации (например, pan).
Ручная верстка
Проблемы:
- При неправильной реализации — есть hardcoded-значения (при правильной, только padding-и и прочее; причем это можно вынести в отдельных header).
- Больше кода (приблизительно в 1.5 раза).
- Ваши предложения?
Плюсы:
- При правильной реализации все очень гибко.
- Работает быстрей.
- Можно рассчитать фреймы в background-потоке и просто их потом применить.
Итог
При отказе от каждого из [Stobyboard, Xib, Autolayout] работа усложняется и кода становится больше.
Имеет смысл не использовать Storyboard (не обязательно полностью), если:
- Делаем сложные анимации (отпадает необходимость кидать кучу outlet-ов).
- Время восстановления из архива становится критичным.
- Возникает много костылей (обычно, большая их часть устраняется правильной архитектурой и/или method swizzling-ом).
Имеет смысл, требовательные к ресурсам TableView/CollectionView не делать ни в Storyboard, ни в Xib (мы теряем время на парсинге файла, теряем гибкость). При оптимизации, можно сначала сверстать с использованием autolayouts, но если лаги не проходят — замерить в инструментах, убедиться, что именно layout-ы тормозят, и потом уже переходить на ручной счет.
Иногда, похожее содержимое необходимо отображать как в TableViewCell так и в CollectionViewCell. При ручной верстке это не будет проблемой. А при использовании Xib-а решается, например, так: содержимое ячейки верстаем в xib-файле, а в init-е ячейки вызывать addSubview с данным view.
EDIT:
Подводя итог, можно сказать, что Storyboard имеет следующие преимущества перед ручной версткой:
- Наглядность (верстка в коде, зачастую, «понятна» только ее автору — см. комментарий DmitrySpb79).
- Использование нескольких Storyboard позволяет разделить различные user-story и уменьшает вероятность конфликтов при командной разработке.
- При необходимости использовать в одном из сторибордов конроллер, из другого сториборда — с версии iOS 8 можем использовать Storyboard Reference и этот комментарий от visput; если нужно поддерживать iOS версий < 8, то можно использовать Xib-ы, либо самим как-то реализовать аналог Storyboard Reference
Ручную верстку используем там, где это необходимо, а на фреймы переходим — если autolayout становится узким местом (см. комментарий mish).
Комментарии (41)
PapaBubaDiop
20.11.2015 20:39С изумлением смотрю на storyboard, который никак не вписывается в современную модульность. Для игр использую xib+*.h+*.m для каждого игрового экрана. И никаких auto. Два xib (*.xib и *_iPad.xib) для каждого ViewController, если игра универсальная.
egormerkushev
20.11.2015 23:01«И никаких auto» — безе Auto Layout? А как вы на зоопарк диагоналей делаете тогда интерфейс под iPhone?
DevAndrew
20.11.2015 23:57На сколько понимаю в играх проще задавать относительные значения.
egormerkushev
21.11.2015 00:03Ну, это как посмотреть, тот же AL позволяет размещать элементы на экране пропорционально сторонам, у меня есть успешный опыт.
Тут интересно именно тем, как сделать тянущийся интерфейс в Storyboard не прибегая к Auto Layout. Особенно на новый айпадах со Split View…
PapaBubaDiop
21.11.2015 00:29Свободу попугаям! В смысле нет зоопарка. Выкидываю LaunchScreen.xib, предлагаемый по умолчанию, делаю две заставки 640х1136 и 640х960 (их все равно рисовать) и все.
Поскольку доля iPhone 4 мала, я использую один и тот же xib, просто на 4-ке не показываю нижний рекламный баннер) Пусть старички порадуются)
Migun
21.11.2015 09:27Вы не упомянули вопросы локализации. Interface Builder в этом очень неплохо помогает.
Naftic
21.11.2015 11:10Не могу согласиться с этим утверждением. Во-первых, появляются Base.lproj/en.lproj директории и наш Storyboard среди фалов находить становится трудней. Во-вторых, NSLocalizedString на много удобней. И наконец, если мы удалили View и добавили другую, скажем, с аналогичным содержанием (например, был toolbar решили сделать все в custom view) у нее будет другой идентификатор и, соответственно, ее нужно опять переводить.
Migun
21.11.2015 11:21Ну по моему опыту — руками делать все в NSLocalizedString можно только до определенного объема, дальше управлять этим хозяйством становится сложно, руками разбивать на различные Tables и так далее. В IB есть еще замечательный Preview, включая «Double-Length Pseudolanguage», как без него тестировать AutoLayout на реакцию на различные переводы?
egormerkushev
21.11.2015 11:45Как работать с файлами строк локализации, если они меняются постоянно, если меняется структура разметки, идентификаторы и т.п. Пробовал раз — ничего не вышло, пробовал скрипты для инкрементных изменений в переводах — не вышло. Пришел к выводу, что проще в IB вставлять ключи от localizable strings а при загрузке view пробегать по всей иерархии и заменять на тексты из файлов строк…
KamiSempai
23.11.2015 00:28Можно по подробнее? Где именно вы эти ключи вставляете?
egormerkushev
23.11.2015 10:03Ну, это еще не апробированный способ, просто решил, что в следующий раз так сделаю.
Прямо в IB в xib и storyboard в текстовые объеты (UILabel, UIButton etc.) вставлять ключи от localizable strings типа «main-screen.cancel».
Потом в loadView или в viewDidLoad пробегаться и заменять уже на NSLocalizableString(...). Как-то так. Если это плохое решение, напишите, я подумаю.KamiSempai
23.11.2015 15:21Многие не хотят прописывать ключи в строковые значения из за опасности не перевести их. Тогда пользователь увидит строковый код вместо человеческого английского.
Возможно, для хранения лучше подойдет accessabilityIdentifier, но меня смущает назначение других параметров с аналогичным названием. Например, accessibilityLabel используется слабовидящими для озвучивания элемента.
Хотя, если accessabilityIdentifier используется некоторыми программистами для тестирования, почему бы не использовать его и для локализации?egormerkushev
23.11.2015 15:25Если будет упущен в какой-либо части перевод на какой-нибудь язык, то ключи в итоге вылезут в интерфейсе, так как NSLocalizableString вернет ключ, если для него нет значения в строках…
Мне кажется, мой такой пусть пока гипотетический подход более консистентный что ли, чем логический «биндинг» элементов интерфейса в коде… Бывает, что элемент не используется абсолютно никак в логике работы приложения и отвечает только за визуальную часть, но всё равно для перевода требуется делать outlet к нему и переводить в коде руками.
Я пробовал когда-то вот это http://oleb.net/blog/2013/02/automating-strings-extraction-from-storyboards-for-localization/, но не пошло.KamiSempai
23.11.2015 17:24Если будет упущен в какой-либо части перевод на какой-нибудь язык, то ключи в итоге вылезут в интерфейсе, так как NSLocalizableString вернет ключ, если для него нет значения в строках…
Если NSLocalizableString возвращает ключ можно не менять текст а оставить значение из IB.
По поводу биндинга полностью согласен. Когда я пришел в iOS из Android, для меня это было дикостью. Но когда, на одной конференции, я узнал что в MailRu применяют этот подход, мой мир вообще рухнул.
YourDestiny
21.11.2015 14:48> Код с анимациями туда не поместить.
Вообще неплохо решается на кастомных UIStoryboardSegue.
> Если их несколько, то при переходе с одного на другой — создается нагрузка на main thread (NSKeyedArchiver должен распарсить сториборду, а он сам по себе медленный).
Серьезно, несколько переходов между сторибордами не окажут сколько-нибудь значимого влияния на производительность :)
> Нельзя все сделать в Storybaord при все желании. Например, задать cornerRadius, shadowOffset и т.д.
Как уже отмечали выше, IBDesignable, либо User-defined runtime attributes.
Идеальный с точки зрения удобства работы и поддержки вариант — N сториборд + вынесение реиспользуемых элементов в xib'ы. Скатываться в чрезмерную оптимизацию имеет смысл только при наличии фактических проблем с производительносью, при том же скролле сложных таблиц. Лэйаут в коде обычно выглядит понятным только для его автора, а вот остальным приходится тяжелее.visput
23.11.2015 00:44вынесение реиспользуемых элементов в xib'ы
Для реиспльзуемых элементов можно использовать ContainerView + Embed Segue в storyboard.
Этот вариант удобней тем, что прямо в storyboard вы настраиваете какой контрой в какое место вставлять, то есть не нужно писать дополнительный код как в случае с xib.egormerkushev
23.11.2015 10:12Это будет работать, например, с ячейками таблиц и коллекшн вью?
visput
23.11.2015 19:48Да, все будет работать.
Пример реюзания контроллера с таблицей в двух других (родительских) контроллерахshergin
22.11.2015 09:55Спасибо большое за Cartography! Я так очарован его дизайном, что кажется SnapKit я больше использовать не буду.
svyatogor
22.11.2015 12:28Каждая моя попытка использовать Storyboard или XIB заканчивается тем что весь дизайн в нем все равно не настроишь и все равно приходится часть делать в коде, получаем на выходе кашу, часть значений настроена тут, часть тут. Для себя я остановился на ручной верстке с использование autolayout там где это оправдано и autosizing там где важен перформанс. Благо при разработке на rubymotion есть множество библиотек облегчающих именно такой подход и предоставляющих очень элегантные обертки.
Naftic
22.11.2015 22:26В rubymotion и storyboard-ы есть?
svyatogor
22.11.2015 23:10в rubymotion можно использовать все то же, что и в cocoa touch, включая xib и storyboard (которые конечно придется рисовать в xcode), есть даже несколько гемов облегчающих жизнь с ними. но по моим ощущениям они особой популярностью не пользуются, может быть потому, что нет интеграции с редактором кода и связи IBOutlet приходится руками создавать.
Naftic
23.11.2015 13:50Просмотрел rubymotion по диагонали — вещь конечно интересная, но ruby лишен типизации, а это, на мой взгляд, очень серьезная беда. Там свои свой рантайм (не MRI), но есть ли там типизация я не понял. Не могли бы прояснить?
К тому же нет protocol-ов — это совсем плохо… Как вы без них живете? или они есть?svyatogor
23.11.2015 14:46На самом деле идологически ruby и objective c достаточно близки. В обоих языках методы это все го лишь функции и вызов осуществляется отправкой сообщения, что позволяет в рантайме добавлять методы или даже подменять существующие.
Типов нет, это язык динамический этапа присвоения (т.е. тип прозрачно назначается в момент присвоения значения). Нельзя сказать что это плохо, это просто другой подход, дающий очень очень большую свободу но и несущий определнный риск.
Протоколов в самом руби нет, т.к. они просто не нужны. Вместо того чтобы говорит «дай мне объект реализующий такой то протокол», в руби всегда можно проверить «а вот эта балалайка поймет если я позову у нее метод :abc? нет? ну значит кинем exception».
В общем rubymotion позволяет писать приложение намного быстрее (особенно если вы хорошо знаете ruby) благодаря огромной гибкости языка и наличию большого количества библиотек, которые скрывают за собой сложности Cocoa в тех случая когда они не нужны.Naftic
23.11.2015 18:42Язык ruby я знаю достаточно хорошо, и с крутостью рантаймов ruby/objc (идеологических аналогов smalltalk) тоже знаком. Но такая гибкость часто выходит боком — слишком многое приходится хранить в своей голове. Тогда как при строгой типизации — компилятор очень часто спасает от запоминания типов функций, переменных и тд.
Есть такая штука protocol-oriented programming, которая мне очень нравится, особенно в swift 2. К тому же я не могу себе представить более или менее гибкую архитектуру без абстрактных интерфейсов (т.е. protocol).
Не буду разводить холивар, но мне комфортней работать со статической типизацией. Я никого к этом не призываю, просто мне так удобнее.
Есть ли руби с типами?
DmitrySpb79
22.11.2015 13:08По-моему при более-менее сложном дизайне — ручная верстка превратится в ад. Все-таки мышью и красивее и нагляднее, и что-то поменять можно в 2 клика.
Другой вопрос, что для видимо для некоторых разработчиков, перешедших в iOS из web-а, такой подход вполне привычный. Но по сути это имхо позавчерашний день, сродни написанию кода в notepad-e и уверению всех что это круто.
Главный минус storyboard для меня, это сложность с командной работой, в этом плане XIB еще долго будет жить. А насчет скорости, честно говоря не понял, в каких задачах распаковка из NIB будет узким местом? Есть ли какие-то реальные замеры где что-то тормозило бы, или это оптимизация ради оптимизации?
Имхо 95% поведения viewcontroller-a может быть задано в storyboard (constrains, size-classes), оставшиеся 5% можно и закодить.visput
23.11.2015 00:46Главный минус storyboard для меня, это сложность с командной работой
Можете пояснить в чем сложность использования storyboard в случае командной работы?DmitrySpb79
23.11.2015 10:11Storyboard это не тот формат файлов, где было бы удобно исправлять конфликты в git :)
Кстати только вчера попробовал после этой статьи, хранить связи в storyboard а сам диалог в xib, загружая его в loadView, вполне работает. Т.е. вполне можно совмещать визуальную наглядность связей storyboard и легкость раздельного редактирования ресурсов.visput
23.11.2015 20:10Я спросил к тому, что формат сторибордов и xib был сильно улучшен в последних версиях Xcode (стал более читаемым и структурированным). У нас в команде достаточно редко возникают конфликты при одновременном изменении сториборда (на проекте 4 человека).
Конфликты возникают, когда несколько разработчиков одновременно меняют один и тот же контроллер. Но это те же конфликты, что и в случае с отдельным xib для каждого контроллера.
corristo
22.11.2015 14:23Если их несколько, то при переходе с одного на другой — создается нагрузка на main thread (NSKeyedArchiver должен распарсить сториборду, а он сам по себе медленный).
В скомпилированном виде сториборд представляет из себя набор nib-файлов и небольшого файлика с метаданными. То есть при создании инстанса UIStoryboard «распарсить» надо только метаданные. А грузить при переходе VC из nib-файла придется в любом случае, даже если сториборд у вас один.Naftic
22.11.2015 22:28Хм, понятно. Дело в том, что при загрузке сториборды у нас, по не очевидной причине анимация дергалась. Я во всем винил процесс декодирования, видимо, зря…
RedRover
22.11.2015 17:40Я бы сказал что сейчас стоит использовать (и вообще задумываться) о ручной верстке в двух случаях:
1. Когда требуется что-то что нельзя задать в рамках Auto Layout, но с таким я сталкивался всего два раза.
2. Требуется считать раскладку очень быстро, например, множество сложных ячеек в таблице.
Во всех остальных случаях ручная верстка дает больше проблем чем пользы, как правильно заметил автор очень много кода, который потом еще надо и как-то поддерживать.
AcidLynx
23.11.2015 07:28Самый жирный минус сториборда — невозможность нормального merge при совместной разработке. Xib мержить немного проще (за счет того, что там обычно вьюхи для каждого отдельного вьюконтроллера).
mish
>Нельзя все сделать в Storybaord при все желании. Например, задать cornerRadius, shadowOffset
IBInspectable или задавать через user defined runtime attributes (как собственно IBInspectable и работает)
Тема, конечно, холиварная, но раскрыта поверхностно. Вот немного еще по теме www.raywenderlich.com/51992/storyboards-vs-nibs-vs-code-the-great-debate
Хорошо бы рассмотреть варианты в контексте использования size class'ов.
Ну и не забыть про такой важный момент как наглядность верстки.
Считаю, что нужно отталкиваться от задачи. Начинать однозначно со storyboard/xib. Autolayout'ы использовать в коде, когда есть необходимость (а такое, конечно, бывает). На фреймы переходить только в крайнем случае, исключительно для повышения производительности.