Ручная верстка vs Storyboard/Nib

    Большинство разработчиков для iOS знакомы со Storyboard. Apple предлагает использовать его. Но в последнее время, удобство этого инструмента вызывает у меня сомнения.

    Ниже я хочу сравнить различные методологии с использованием Storyboard или набора Xib-фалов, с ручной верстой (с использованием autolayout и без). Не претендую на полноту раскрытия темы и буду рад, если вы укажете мне на ошибки и/или предложите другие методологии и критерии сравнения.

    Пост не содержит экспериментов, академических вычислений и основывается на моих знаниях и опыте в области iOS разработки. Был бы рад узнать мнения экспертов!

    С теми или иными оговорками, существуют следующие методологии разработки:
    • Один Storyboard и все в нем (минимум кода верстки в коде) — используется в небольших проектах; очень неудобен при разработке командой разработчиков.
    • Несколько Storyobard-ов и все в них.
    • Забываем про Storybaord, все UI-контролы помещаем в xib-файлы (см. этот пост).
    • Забываем про Storyboard и Xib, но используем autolayout — привет PureLayouts.
    • Забываем про все Storyboard/Xib/AutoLayouts и настраиваем всем View фреймы в ручную.
    • Забываем про UIKit — используем сторонние решения AsyncDisplayKit от Facebook или ComponentsKit. (Такого опыта у меня нет, поэтому ничего путного не скажу).

    Storyboard и Xib

    Проблемы:
    1. Большую часть кода, связанного с анимациями туда не поместить.
    2. Если их несколько, то при переходе с одного на другой — создается нагрузка на main thread (NSKeyedArchiver должен распарсить сториборду, а он сам по себе медленный).
    3. Нельзя все сделать в Storybaord при все желании. Например, задать cornerRadius, shadowOffset и т.д.; на самом деле, можно — через user defined runtime attributes (@IBInspectable)

    Плюсы:
    1. Верстка наглядна.
    2. User-story виден при просмотре.
    3. SizeClasses
    4. Используем Embedded Segue/Storyboard Reference и живем счастливо
    5. Мы абстрагируемся от типа перехода и для осуществления самого перехода достаточно знать segue identifier (см. RamblerSegues), также, при использовании автоматических dependency injection библиотек (см. typhoon), мы абстрагируемся от внедрения зависимостей.

    Ручная верстка с использованием Autolayout-ов

    Использовал различные тулы, наиболее, удобные — PureLayout и Cartography (для swift).

    Проблемы:
    1. Больше кода (приблизительно в 1.2 раза).
    2. Нет наглядности
    3. Сложный layout превращается в ад

    Плюсы:
    1. Немного быстрей — экономим на парсинге Xib/Storyboard файла NSKyedArchiver-ом.
    2. Декларативно и все в одном месте (не нужно постоянно переключаться между Storyboard и текстом).
    3. Удобней делать анимации (например, pan).

    Ручная верстка

    Проблемы:
    1. При неправильной реализации — есть hardcoded-значения (при правильной, только padding-и и прочее; причем это можно вынести в отдельных header).
    2. Больше кода (приблизительно в 1.5 раза).
    3. Нет наглядности
    4. Сами нужно заботиться об адекватном изменении frame-а при изменении параметров внешней view
    5. Нет SizeClasses

    Плюсы:
    1. При правильной реализации все очень гибко.
    2. Работает быстрей.
    3. Можно рассчитать фреймы в 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).
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 43
    • +6
      >Нельзя все сделать в 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'ы использовать в коде, когда есть необходимость (а такое, конечно, бывает). На фреймы переходить только в крайнем случае, исключительно для повышения производительности.

      • 0
        С изумлением смотрю на storyboard, который никак не вписывается в современную модульность. Для игр использую xib+*.h+*.m для каждого игрового экрана. И никаких auto. Два xib (*.xib и *_iPad.xib) для каждого ViewController, если игра универсальная.
        • 0
          «И никаких auto» — безе Auto Layout? А как вы на зоопарк диагоналей делаете тогда интерфейс под iPhone?
          • 0
            На сколько понимаю в играх проще задавать относительные значения.
            • 0
              Ну, это как посмотреть, тот же AL позволяет размещать элементы на экране пропорционально сторонам, у меня есть успешный опыт.
              Тут интересно именно тем, как сделать тянущийся интерфейс в Storyboard не прибегая к Auto Layout. Особенно на новый айпадах со Split View…
            • 0
              Свободу попугаям! В смысле нет зоопарка. Выкидываю LaunchScreen.xib, предлагаемый по умолчанию, делаю две заставки 640х1136 и 640х960 (их все равно рисовать) и все.
              Поскольку доля iPhone 4 мала, я использую один и тот же xib, просто на 4-ке не показываю нижний рекламный баннер) Пусть старички порадуются)
        • +2
          Не нашёл вариант Storyboard + Xib когда в сториборде только навигация, а в ксибах сами контроллеры
          • 0
            Думаю это удобнее, чем если все в Storyboard. Однако, верстать в коде мне кажется более удобным вариантом (тем же PureLayouts).
            • 0
              Честно говоря, при первом прочтении я неправильно как-то понял идею… В чем принципиальное отличие от того, чтоб хранить VIewController в самом Storyboard-е?
              • 0
                В комменте ниже, есть более адекватная замена:
                > Для реиспльзуемых элементов можно использовать ContainerView + Embed Segue в storyboard.
                > Этот вариант удобней тем, что прямо в storyboard вы настраиваете какой контрой в какое место вставлять, то есть не нужно писать дополнительный код как в случае с xib.
            • 0
              Вы не упомянули вопросы локализации. Interface Builder в этом очень неплохо помогает.
              • 0
                Не могу согласиться с этим утверждением. Во-первых, появляются Base.lproj/en.lproj директории и наш Storyboard среди фалов находить становится трудней. Во-вторых, NSLocalizedString на много удобней. И наконец, если мы удалили View и добавили другую, скажем, с аналогичным содержанием (например, был toolbar решили сделать все в custom view) у нее будет другой идентификатор и, соответственно, ее нужно опять переводить.
                • 0
                  Ну по моему опыту — руками делать все в NSLocalizedString можно только до определенного объема, дальше управлять этим хозяйством становится сложно, руками разбивать на различные Tables и так далее. В IB есть еще замечательный Preview, включая «Double-Length Pseudolanguage», как без него тестировать AutoLayout на реакцию на различные переводы?
                  • 0
                    Как работать с файлами строк локализации, если они меняются постоянно, если меняется структура разметки, идентификаторы и т.п. Пробовал раз — ничего не вышло, пробовал скрипты для инкрементных изменений в переводах — не вышло. Пришел к выводу, что проще в IB вставлять ключи от localizable strings а при загрузке view пробегать по всей иерархии и заменять на тексты из файлов строк…
                    • 0
                      Можно по подробнее? Где именно вы эти ключи вставляете?
                      • 0
                        Ну, это еще не апробированный способ, просто решил, что в следующий раз так сделаю.
                        Прямо в IB в xib и storyboard в текстовые объеты (UILabel, UIButton etc.) вставлять ключи от localizable strings типа «main-screen.cancel».
                        Потом в loadView или в viewDidLoad пробегаться и заменять уже на NSLocalizableString(...). Как-то так. Если это плохое решение, напишите, я подумаю.
                        • 0
                          Многие не хотят прописывать ключи в строковые значения из за опасности не перевести их. Тогда пользователь увидит строковый код вместо человеческого английского.
                          Возможно, для хранения лучше подойдет accessabilityIdentifier, но меня смущает назначение других параметров с аналогичным названием. Например, accessibilityLabel используется слабовидящими для озвучивания элемента.
                          Хотя, если accessabilityIdentifier используется некоторыми программистами для тестирования, почему бы не использовать его и для локализации?
                          • 0
                            Если будет упущен в какой-либо части перевод на какой-нибудь язык, то ключи в итоге вылезут в интерфейсе, так как NSLocalizableString вернет ключ, если для него нет значения в строках…
                            Мне кажется, мой такой пусть пока гипотетический подход более консистентный что ли, чем логический «биндинг» элементов интерфейса в коде… Бывает, что элемент не используется абсолютно никак в логике работы приложения и отвечает только за визуальную часть, но всё равно для перевода требуется делать outlet к нему и переводить в коде руками.

                            Я пробовал когда-то вот это http://oleb.net/blog/2013/02/automating-strings-extraction-from-storyboards-for-localization/, но не пошло.
                            • 0
                              Если будет упущен в какой-либо части перевод на какой-нибудь язык, то ключи в итоге вылезут в интерфейсе, так как NSLocalizableString вернет ключ, если для него нет значения в строках…
                              Если NSLocalizableString возвращает ключ можно не менять текст а оставить значение из IB.
                              По поводу биндинга полностью согласен. Когда я пришел в iOS из Android, для меня это было дикостью. Но когда, на одной конференции, я узнал что в MailRu применяют этот подход, мой мир вообще рухнул.
                • +1
                  > Код с анимациями туда не поместить.
                  Вообще неплохо решается на кастомных UIStoryboardSegue.

                  > Если их несколько, то при переходе с одного на другой — создается нагрузка на main thread (NSKeyedArchiver должен распарсить сториборду, а он сам по себе медленный).
                  Серьезно, несколько переходов между сторибордами не окажут сколько-нибудь значимого влияния на производительность :)

                  > Нельзя все сделать в Storybaord при все желании. Например, задать cornerRadius, shadowOffset и т.д.
                  Как уже отмечали выше, IBDesignable, либо User-defined runtime attributes.

                  Идеальный с точки зрения удобства работы и поддержки вариант — N сториборд + вынесение реиспользуемых элементов в xib'ы. Скатываться в чрезмерную оптимизацию имеет смысл только при наличии фактических проблем с производительносью, при том же скролле сложных таблиц. Лэйаут в коде обычно выглядит понятным только для его автора, а вот остальным приходится тяжелее.
                  • +1
                    вынесение реиспользуемых элементов в xib'ы

                    Для реиспльзуемых элементов можно использовать ContainerView + Embed Segue в storyboard.
                    Этот вариант удобней тем, что прямо в storyboard вы настраиваете какой контрой в какое место вставлять, то есть не нужно писать дополнительный код как в случае с xib.
                    • 0
                      Это будет работать, например, с ячейками таблиц и коллекшн вью?
                      • 0
                        Да, все будет работать.

                        Пример реюзания контроллера с таблицей в двух других (родительских) контроллерах
                        image
                      • 0
                        А если из двух разных сторибородов надо одну и ту же вью взять?
                        • +1
                          Для этих целей можно использовать объект Storyboard Reference. Он позволяет экспортировать контроллеры из одного сториборда в другие.
                          Правда эта фича доступна только с iOS 8.

                          Как это выглядит?

                    • 0
                      Спасибо большое за Cartography! Я так очарован его дизайном, что кажется SnapKit я больше использовать не буду.
                      • +1
                        Каждая моя попытка использовать Storyboard или XIB заканчивается тем что весь дизайн в нем все равно не настроишь и все равно приходится часть делать в коде, получаем на выходе кашу, часть значений настроена тут, часть тут. Для себя я остановился на ручной верстке с использование autolayout там где это оправдано и autosizing там где важен перформанс. Благо при разработке на rubymotion есть множество библиотек облегчающих именно такой подход и предоставляющих очень элегантные обертки.
                        • 0
                          В rubymotion и storyboard-ы есть?
                          • 0
                            в rubymotion можно использовать все то же, что и в cocoa touch, включая xib и storyboard (которые конечно придется рисовать в xcode), есть даже несколько гемов облегчающих жизнь с ними. но по моим ощущениям они особой популярностью не пользуются, может быть потому, что нет интеграции с редактором кода и связи IBOutlet приходится руками создавать.
                            • 0
                              Просмотрел rubymotion по диагонали — вещь конечно интересная, но ruby лишен типизации, а это, на мой взгляд, очень серьезная беда. Там свои свой рантайм (не MRI), но есть ли там типизация я не понял. Не могли бы прояснить?

                              К тому же нет protocol-ов — это совсем плохо… Как вы без них живете? или они есть?
                              • 0
                                На самом деле идологически ruby и objective c достаточно близки. В обоих языках методы это все го лишь функции и вызов осуществляется отправкой сообщения, что позволяет в рантайме добавлять методы или даже подменять существующие.

                                Типов нет, это язык динамический этапа присвоения (т.е. тип прозрачно назначается в момент присвоения значения). Нельзя сказать что это плохо, это просто другой подход, дающий очень очень большую свободу но и несущий определнный риск.

                                Протоколов в самом руби нет, т.к. они просто не нужны. Вместо того чтобы говорит «дай мне объект реализующий такой то протокол», в руби всегда можно проверить «а вот эта балалайка поймет если я позову у нее метод :abc? нет? ну значит кинем exception».

                                В общем rubymotion позволяет писать приложение намного быстрее (особенно если вы хорошо знаете ruby) благодаря огромной гибкости языка и наличию большого количества библиотек, которые скрывают за собой сложности Cocoa в тех случая когда они не нужны.
                                • 0
                                  Язык ruby я знаю достаточно хорошо, и с крутостью рантаймов ruby/objc (идеологических аналогов smalltalk) тоже знаком. Но такая гибкость часто выходит боком — слишком многое приходится хранить в своей голове. Тогда как при строгой типизации — компилятор очень часто спасает от запоминания типов функций, переменных и тд.

                                  Есть такая штука protocol-oriented programming, которая мне очень нравится, особенно в swift 2. К тому же я не могу себе представить более или менее гибкую архитектуру без абстрактных интерфейсов (т.е. protocol).

                                  Не буду разводить холивар, но мне комфортней работать со статической типизацией. Я никого к этом не призываю, просто мне так удобнее.

                                  Есть ли руби с типами?
                        • +2
                          По-моему при более-менее сложном дизайне — ручная верстка превратится в ад. Все-таки мышью и красивее и нагляднее, и что-то поменять можно в 2 клика.

                          Другой вопрос, что для видимо для некоторых разработчиков, перешедших в iOS из web-а, такой подход вполне привычный. Но по сути это имхо позавчерашний день, сродни написанию кода в notepad-e и уверению всех что это круто.

                          Главный минус storyboard для меня, это сложность с командной работой, в этом плане XIB еще долго будет жить. А насчет скорости, честно говоря не понял, в каких задачах распаковка из NIB будет узким местом? Есть ли какие-то реальные замеры где что-то тормозило бы, или это оптимизация ради оптимизации?

                          Имхо 95% поведения viewcontroller-a может быть задано в storyboard (constrains, size-classes), оставшиеся 5% можно и закодить.
                          • 0
                            Главный минус storyboard для меня, это сложность с командной работой

                            Можете пояснить в чем сложность использования storyboard в случае командной работы?
                            • +1
                              Storyboard это не тот формат файлов, где было бы удобно исправлять конфликты в git :)

                              Кстати только вчера попробовал после этой статьи, хранить связи в storyboard а сам диалог в xib, загружая его в loadView, вполне работает. Т.е. вполне можно совмещать визуальную наглядность связей storyboard и легкость раздельного редактирования ресурсов.
                              • 0
                                Я спросил к тому, что формат сторибордов и xib был сильно улучшен в последних версиях Xcode (стал более читаемым и структурированным). У нас в команде достаточно редко возникают конфликты при одновременном изменении сториборда (на проекте 4 человека).
                                Конфликты возникают, когда несколько разработчиков одновременно меняют один и тот же контроллер. Но это те же конфликты, что и в случае с отдельным xib для каждого контроллера.
                        • +3
                          Если их несколько, то при переходе с одного на другой — создается нагрузка на main thread (NSKeyedArchiver должен распарсить сториборду, а он сам по себе медленный).

                          В скомпилированном виде сториборд представляет из себя набор nib-файлов и небольшого файлика с метаданными. То есть при создании инстанса UIStoryboard «распарсить» надо только метаданные. А грузить при переходе VC из nib-файла придется в любом случае, даже если сториборд у вас один.
                          • 0
                            Хм, понятно. Дело в том, что при загрузке сториборды у нас, по не очевидной причине анимация дергалась. Я во всем винил процесс декодирования, видимо, зря…
                          • +2
                            Я бы сказал что сейчас стоит использовать (и вообще задумываться) о ручной верстке в двух случаях:

                            1. Когда требуется что-то что нельзя задать в рамках Auto Layout, но с таким я сталкивался всего два раза.
                            2. Требуется считать раскладку очень быстро, например, множество сложных ячеек в таблице.

                            Во всех остальных случаях ручная верстка дает больше проблем чем пользы, как правильно заметил автор очень много кода, который потом еще надо и как-то поддерживать.
                            • 0
                              Самый жирный минус сториборда — невозможность нормального merge при совместной разработке. Xib мержить немного проще (за счет того, что там обычно вьюхи для каждого отдельного вьюконтроллера).
                              • 0
                                Это по большей части решается использованием нескольких сторибордов.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.