Большинство разработчиков для 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 и т.д.

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

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

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

Проблемы:
  1. Больше кода (приблизительно в 1.2 раза).
  2. Ваши предложения?

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

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

Проблемы:
  1. При неправильной реализации — есть hardcoded-значения (при правильной, только padding-и и прочее; причем это можно вынести в отдельных header).
  2. Больше кода (приблизительно в 1.5 раза).
  3. Ваши предложения?

Плюсы:
  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).

Комментарии (41)


  1. mish
    20.11.2015 20:19

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


  1. PapaBubaDiop
    20.11.2015 20:39

    С изумлением смотрю на storyboard, который никак не вписывается в современную модульность. Для игр использую xib+*.h+*.m для каждого игрового экрана. И никаких auto. Два xib (*.xib и *_iPad.xib) для каждого ViewController, если игра универсальная.


    1. egormerkushev
      20.11.2015 23:01

      «И никаких auto» — безе Auto Layout? А как вы на зоопарк диагоналей делаете тогда интерфейс под iPhone?


      1. DevAndrew
        20.11.2015 23:57

        На сколько понимаю в играх проще задавать относительные значения.


        1. egormerkushev
          21.11.2015 00:03

          Ну, это как посмотреть, тот же AL позволяет размещать элементы на экране пропорционально сторонам, у меня есть успешный опыт.
          Тут интересно именно тем, как сделать тянущийся интерфейс в Storyboard не прибегая к Auto Layout. Особенно на новый айпадах со Split View…


      1. PapaBubaDiop
        21.11.2015 00:29

        Свободу попугаям! В смысле нет зоопарка. Выкидываю LaunchScreen.xib, предлагаемый по умолчанию, делаю две заставки 640х1136 и 640х960 (их все равно рисовать) и все.
        Поскольку доля iPhone 4 мала, я использую один и тот же xib, просто на 4-ке не показываю нижний рекламный баннер) Пусть старички порадуются)


        1. egormerkushev
          21.11.2015 17:40

          А, теперь понятно.


  1. daspisch
    21.11.2015 01:09

    Не нашёл вариант Storyboard + Xib когда в сториборде только навигация, а в ксибах сами контроллеры


    1. Naftic
      21.11.2015 11:23

      Думаю это удобнее, чем если все в Storyboard. Однако, верстать в коде мне кажется более удобным вариантом (тем же PureLayouts).


  1. Migun
    21.11.2015 09:27

    Вы не упомянули вопросы локализации. Interface Builder в этом очень неплохо помогает.


    1. Naftic
      21.11.2015 11:10

      Не могу согласиться с этим утверждением. Во-первых, появляются Base.lproj/en.lproj директории и наш Storyboard среди фалов находить становится трудней. Во-вторых, NSLocalizedString на много удобней. И наконец, если мы удалили View и добавили другую, скажем, с аналогичным содержанием (например, был toolbar решили сделать все в custom view) у нее будет другой идентификатор и, соответственно, ее нужно опять переводить.


      1. Migun
        21.11.2015 11:21

        Ну по моему опыту — руками делать все в NSLocalizedString можно только до определенного объема, дальше управлять этим хозяйством становится сложно, руками разбивать на различные Tables и так далее. В IB есть еще замечательный Preview, включая «Double-Length Pseudolanguage», как без него тестировать AutoLayout на реакцию на различные переводы?


      1. egormerkushev
        21.11.2015 11:45

        Как работать с файлами строк локализации, если они меняются постоянно, если меняется структура разметки, идентификаторы и т.п. Пробовал раз — ничего не вышло, пробовал скрипты для инкрементных изменений в переводах — не вышло. Пришел к выводу, что проще в IB вставлять ключи от localizable strings а при загрузке view пробегать по всей иерархии и заменять на тексты из файлов строк…


        1. KamiSempai
          23.11.2015 00:28

          Можно по подробнее? Где именно вы эти ключи вставляете?


          1. egormerkushev
            23.11.2015 10:03

            Ну, это еще не апробированный способ, просто решил, что в следующий раз так сделаю.
            Прямо в IB в xib и storyboard в текстовые объеты (UILabel, UIButton etc.) вставлять ключи от localizable strings типа «main-screen.cancel».
            Потом в loadView или в viewDidLoad пробегаться и заменять уже на NSLocalizableString(...). Как-то так. Если это плохое решение, напишите, я подумаю.


            1. KamiSempai
              23.11.2015 15:21

              Многие не хотят прописывать ключи в строковые значения из за опасности не перевести их. Тогда пользователь увидит строковый код вместо человеческого английского.
              Возможно, для хранения лучше подойдет accessabilityIdentifier, но меня смущает назначение других параметров с аналогичным названием. Например, accessibilityLabel используется слабовидящими для озвучивания элемента.
              Хотя, если accessabilityIdentifier используется некоторыми программистами для тестирования, почему бы не использовать его и для локализации?


              1. egormerkushev
                23.11.2015 15:25

                Если будет упущен в какой-либо части перевод на какой-нибудь язык, то ключи в итоге вылезут в интерфейсе, так как NSLocalizableString вернет ключ, если для него нет значения в строках…
                Мне кажется, мой такой пусть пока гипотетический подход более консистентный что ли, чем логический «биндинг» элементов интерфейса в коде… Бывает, что элемент не используется абсолютно никак в логике работы приложения и отвечает только за визуальную часть, но всё равно для перевода требуется делать outlet к нему и переводить в коде руками.

                Я пробовал когда-то вот это http://oleb.net/blog/2013/02/automating-strings-extraction-from-storyboards-for-localization/, но не пошло.


                1. KamiSempai
                  23.11.2015 17:24

                  Если будет упущен в какой-либо части перевод на какой-нибудь язык, то ключи в итоге вылезут в интерфейсе, так как NSLocalizableString вернет ключ, если для него нет значения в строках…
                  Если NSLocalizableString возвращает ключ можно не менять текст а оставить значение из IB.
                  По поводу биндинга полностью согласен. Когда я пришел в iOS из Android, для меня это было дикостью. Но когда, на одной конференции, я узнал что в MailRu применяют этот подход, мой мир вообще рухнул.


  1. YourDestiny
    21.11.2015 14:48

    > Код с анимациями туда не поместить.
    Вообще неплохо решается на кастомных UIStoryboardSegue.

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

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

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


    1. visput
      23.11.2015 00:44

      вынесение реиспользуемых элементов в xib'ы

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


      1. egormerkushev
        23.11.2015 10:12

        Это будет работать, например, с ячейками таблиц и коллекшн вью?


        1. visput
          23.11.2015 19:48

          Да, все будет работать.

          Пример реюзания контроллера с таблицей в двух других (родительских) контроллерах
          image


      1. Naftic
        23.11.2015 13:46

        А если из двух разных сторибородов надо одну и ту же вью взять?


        1. visput
          23.11.2015 20:05

          Для этих целей можно использовать объект Storyboard Reference. Он позволяет экспортировать контроллеры из одного сториборда в другие.
          Правда эта фича доступна только с iOS 8.

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


  1. shergin
    22.11.2015 09:55

    Спасибо большое за Cartography! Я так очарован его дизайном, что кажется SnapKit я больше использовать не буду.


  1. svyatogor
    22.11.2015 12:28

    Каждая моя попытка использовать Storyboard или XIB заканчивается тем что весь дизайн в нем все равно не настроишь и все равно приходится часть делать в коде, получаем на выходе кашу, часть значений настроена тут, часть тут. Для себя я остановился на ручной верстке с использование autolayout там где это оправдано и autosizing там где важен перформанс. Благо при разработке на rubymotion есть множество библиотек облегчающих именно такой подход и предоставляющих очень элегантные обертки.


    1. Naftic
      22.11.2015 22:26

      В rubymotion и storyboard-ы есть?


      1. svyatogor
        22.11.2015 23:10

        в rubymotion можно использовать все то же, что и в cocoa touch, включая xib и storyboard (которые конечно придется рисовать в xcode), есть даже несколько гемов облегчающих жизнь с ними. но по моим ощущениям они особой популярностью не пользуются, может быть потому, что нет интеграции с редактором кода и связи IBOutlet приходится руками создавать.


        1. Naftic
          23.11.2015 13:50

          Просмотрел rubymotion по диагонали — вещь конечно интересная, но ruby лишен типизации, а это, на мой взгляд, очень серьезная беда. Там свои свой рантайм (не MRI), но есть ли там типизация я не понял. Не могли бы прояснить?

          К тому же нет protocol-ов — это совсем плохо… Как вы без них живете? или они есть?


          1. svyatogor
            23.11.2015 14:46

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

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

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

            В общем rubymotion позволяет писать приложение намного быстрее (особенно если вы хорошо знаете ruby) благодаря огромной гибкости языка и наличию большого количества библиотек, которые скрывают за собой сложности Cocoa в тех случая когда они не нужны.


            1. Naftic
              23.11.2015 18:42

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

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

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

              Есть ли руби с типами?


  1. DmitrySpb79
    22.11.2015 13:08

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

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

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

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


    1. visput
      23.11.2015 00:46

      Главный минус storyboard для меня, это сложность с командной работой

      Можете пояснить в чем сложность использования storyboard в случае командной работы?


      1. DmitrySpb79
        23.11.2015 10:11

        Storyboard это не тот формат файлов, где было бы удобно исправлять конфликты в git :)

        Кстати только вчера попробовал после этой статьи, хранить связи в storyboard а сам диалог в xib, загружая его в loadView, вполне работает. Т.е. вполне можно совмещать визуальную наглядность связей storyboard и легкость раздельного редактирования ресурсов.


        1. visput
          23.11.2015 20:10

          Я спросил к тому, что формат сторибордов и xib был сильно улучшен в последних версиях Xcode (стал более читаемым и структурированным). У нас в команде достаточно редко возникают конфликты при одновременном изменении сториборда (на проекте 4 человека).
          Конфликты возникают, когда несколько разработчиков одновременно меняют один и тот же контроллер. Но это те же конфликты, что и в случае с отдельным xib для каждого контроллера.


          1. DmitrySpb79
            23.11.2015 21:06

            Тут да, согласен.


  1. corristo
    22.11.2015 14:23

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

    В скомпилированном виде сториборд представляет из себя набор nib-файлов и небольшого файлика с метаданными. То есть при создании инстанса UIStoryboard «распарсить» надо только метаданные. А грузить при переходе VC из nib-файла придется в любом случае, даже если сториборд у вас один.


    1. Naftic
      22.11.2015 22:28

      Хм, понятно. Дело в том, что при загрузке сториборды у нас, по не очевидной причине анимация дергалась. Я во всем винил процесс декодирования, видимо, зря…


  1. RedRover
    22.11.2015 17:40

    Я бы сказал что сейчас стоит использовать (и вообще задумываться) о ручной верстке в двух случаях:

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

    Во всех остальных случаях ручная верстка дает больше проблем чем пользы, как правильно заметил автор очень много кода, который потом еще надо и как-то поддерживать.


  1. AcidLynx
    23.11.2015 07:28

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


    1. Naftic
      23.11.2015 13:52

      Это по большей части решается использованием нескольких сторибордов.