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 present the 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. Но об этом будет уже другой пост.

    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 48

      0

      До сих пор для нас лучший способ не работать со storyboards…
      Пост красивый спасибо!

        0
        Знаете, применив описанный в посте метод — не соглашусь с вами. Теперь работать стало в разы удобнее. Сториборды открываются мгновенно, распределенная разработка идет прекрасно, все изменения сливаются друг с другом без конфликтов. Я доволен. :)
          +2

          Да, действительно, это решает кучу проблем с мерджем. Но меня убивают автолайауты, вот это таскание мышкой стороны, привязки вьюх, расстановка приоритетов, не знаю либо это мой анскилл, либо это действительно очень не удобное средство для расстановки лайаутов, и при всем при этом, тебе все же нужно залезть в код и все поправить. Потом один сторибоар для одного экрана, по мне дак попахивает костылем, одобренным Apple?
          Хочу пост о том как работают люди в storybords с максимальной производительностью. А пока я буду считать что сотиробды это то что бывает, когда нет хороших дизайнеров.

            +1
            Это точно не ваш анскилл. Тут реально нужно очень долго и нудно этот вопрос изучать. Я иногда натыкаюсь на компании, которым нужен даже не столько разработчик, сколько специалист именно по сторибордам/лэйаутам/констреинтам. :)

            По поводу костыля — не думаю, что это костыль. Один сториборд — один экран, это хорошо как по мне. Можно же и все реализации классов сложить в один файл, но никто (ну почти никто) так не делает, и костылем это совсем не считается. :)

              0
              Потом один сторибоар для одного экрана, по мне дак попахивает костылем

              Согласен с вами, человек использует Storyboard, но ограничивается функциональностью xib. Почему бы сразу не использовать xib'ы? Тогда бы вся статья уложилась в одно предложение и было бы более хорошее решение. Просто: "Лучший способ работать со сторибордами? Не работайте с ними, используйте xib. " — вот и вся статья.


              Но автор решил пойти трудным путем и использует более мощный инструмент лишь на малую его часть. По аналогии, это как взять КАМАЗ для того, что бы перевезти детскую песочную лопатку.

                +1

                Ну у меня есть сториборды и с несколькими экранами. :) Например тот же таб-бар предложенным образом не разные сториборды не разделить. :)

                  +1
                  В xib нет top/bottom layout guide'ов, из-за одного этого storyboard с одним экраном лучше, чем xib.
                  0
                  Можно попробовать библиотеку SnapKit: тот же auto layout, но в коде. Получается эдакая удобная декларативная разметка.
                    0
                    Можно попробовать библиотеку SnapKit: тот же auto layout, но в коде.

                    SnapKit(Swift)/Masonry(Obj-c) да это единственная адекватная либа (из всех что видел на github) позволяющая работать с autolayouts и единственное что спасает идиотский инструмент(autolayouts) от полного отказа его использования.
                    Но мы перешили на ADK и полностью отказались от autolayouts (кроме тех мест где меньше 2 вьюх на экране). И теперь живем очень даже не плохо. Рекомендую.

              0
              А кто-нибудь замерял, на сколько увеличивается вес приложения при дроблении одного сториборда на несколько? Было бы очень интересно узнать)

              И большое спасибо за статью!
                +1
                Вот размеры каталога с проектом до дробления и после (слева — до, справа — после):
                +6

                Swift позволяет создать обобщенное решение, чтобы не копипастить метод создания в каждый класс:


                extension UIViewController {
                    private class func storyboardInstancePrivate<T: UIViewController>() -> T? {
                        let storyboard = UIStoryboard(name: String(describing: self), bundle: nil)
                        return storyboard.instantiateInitialViewController() as? T
                    }
                    class func storyboardInstance() -> Self? {
                        return storyboardInstancePrivate()
                    }
                }
                  0

                  Да, так красивее.


                  Правда у меня есть пара вью-контроллеров вложенных в навигэйшны, и в таких случаях мне нужно, чтоб метод storyboardInstance() возвращал не Self?, а UINavigationController?

                    0
                    Не понял совсем, какая связь между именем (типом) класса контроллера и именем storyboard, который может содержать кучу таких контроллеров, инстанс каждого из которых может понадобиться.
                  +1
                  у меня лично сделано так:
                  1 сториборд только с uiviewcontroller (для проверки есть ли заказы) и uinavigation.
                  2-3 сториборда под свои степы (поиск, процесс заказа).

                  И благодаря тому, что в iOS восхитительно работает горячая замена контроллеров в навигации ( [self setViewControllers:@[...] animated:YES];, ес-но все прекрасно dealloc'ируется), упрощает жизнь при разработке в 100500 раз и не надо запариваться на то, что бы, как перейти с одного контроллера на другой, если так не прописано «логикой».

                    +1
                    Раньше, когда был iOS 7, Xcode 5, трава зеленее, не было никаких сторибордов. Были просто xib'ы, по одному на контроллер (и для UIView иногда). Еще тогда не понимал этой идеи — держать много экранов в одном файле. Рад что некоторые люди, как и я, вернулись к раздельным экранам.
                      0

                      Сейчас xib'ы никуда не делись. Не знаю почему автор использует Storyboard на каждый экран, вместо использования xib'ов. Очень странный подход

                        +2
                        Подход оправдывает себя, если нужно реализовать, скажем, master-detail экраны, или встроить в контроллер UIPageView через UIContainerView. Распостраненный пример: несколько панелей настроек, переключаемых через UISegmentedControl. Эти панельки не переиспользуются на других экранах приложения, и логически объединены в одну группу, потому целесообразно использовать для них один storyboard.
                          0

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

                        +1
                        Идея сторибордов состоит в том, чтобы разместить в одном сториборде один «стори». Например, какой-то процесс из нескольких шагов (визард создания лота в каком-нибудь клоне eBay, процедура вызова такси и т.д.). В теории, это должно было облегчить работу программистов. Но на практике неудобство переиспользования одного и того же вью контроллера в одной «стори» привело к тому, что мы просто отказались от использования сторибордов. Была мысль делать отдельный сториборд для каждого вью контроллера, но стало непонятно, чем это лучше старых привычных xib.
                        0
                        Скажите, а почему тогда storyboard, а не xib?

                        И да, мелкий нюанс про ссылки на storyboard'ы — там помимо ограничения по Xcode, есть и по iOS (если не ошибаюсь, 8.0+) — проверьте перед использованием.
                          0

                          Ограничения по версии iOS (8.0+) есть и в свежем Xcode 8.


                          А если вы имеет ввиду использование storyboard referencing, то ограничение по версии iOS — 9.0+

                          0
                          А еще лучше просто не использовать сториборды вообще.
                            0
                            Уточню. Совсем. Никаких .storyboard, никаких .xib, никакого interface builder, никогда. Я не видел ни одного проекта за шесть лет опыта с IB, который был бы действительно хорошо написан. С .xib — да, было, но .storyboard — всегда ад. Даже если по сториборде на экран.

                            Давайте честно. Зачем нужны сториборды вообще? Вот альтернативный подход, критикуйте:
                            На каждый экран свой контроллер, и свой подкласс UIView. Весь лейаут и взаимодействие с пользователем — во вью, вся бизнесс логика — в контроллере. Контроллер пинает вью напрямую, вью — через протокол делегирования. Лейаут — через обертки аутолейаута по вкусу (KeepLayout), код в -updateContraints. Для ускорения процесса разработки — dyci или аналог (нажал хоткей — изменения в коде инжектировались и экран обновился).

                            Вот накипело уже. От IB одна только головная боль и толпы радостных индусов, размахивающих очередным новым способом не увязнуть в дебрях IB, продолжая использовать IB. Мыши плакали, кололись, но продолжали жрать кактус. Приходишь собеседоваться в большую серьезную компанию, а они пол года ваяют приложение и никуда оно не движется. Смотришь, код вроде бы нормальный, разработчики толковые и адекватные, а в сторибордах очередной срач, IBDesignable падает и не работает, все в каких-то страшных Segue и черт ногу сломает. Зато MVVC, IB, Swift, ага. Вам шашечки или ехать? Надоело переписывать за другими разрабами проекты. Нет, с одной стороны мне за это платят зарплаты, но с другой разобраться в этой дикой вермишели порой бывает без бутылки не возможно и нервы не резиновые.
                              +1

                              Ну описаный подход как раз для того, чтоб не было "в сторибордах очередной срач, IBDesignable падает и не работает, все в каких-то страшных Segue и черт ногу сломает" :)

                                +1
                                Я согласен с тем, что описанный подход улучшает ситуацию. Но это не отвечает на вопрос, который я задал: зачем нужны сториборды вообще? Зачем нужно поддерживать какую-то дополнительную сущность, при работе с которой требуется быть очень осторожным, тратить так много времени на разброску констрейнтов и атрибутов контролов мышкой, заниматься копипастой, пробрасывать IBOutlet'ы и прочая прочая?
                                  +1

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

                                    +2
                                    Это плюс, но разве он оправдывает минусы? Тем более, что всегда можно запустить симулятор и посмотреть в живую. Это не так удобно, с одной стороны, с другой стоимость сторибордов для такой простой вещи оказывается слишком велика, нужны какие-то еще плюсы, чтобы ее оправдать. А с учетом инжектов без перекомпиляции/перезапуска плюс совсем меркнет.
                                +1
                                Бизнес-логика в контроллере? В модели же…
                                  0
                                  На первый взгляд логично, но спорно. Паттерн MVC, как его предоставляет нам Apple, к сожалению толкает разработчиков выносить логику из модели в контроллер. Вопрос тестирования начинает гореть синими пламенем в этот момент и да, в общем случае действительно лучше сопротивляться и выносить все в модель. Но у медали есть обратная сторона: в результате таких действий, если логики на самом деле мало, можно легко нарушить принцип KISS, получить большое количество сущностей и абстракций там, где необходимости в этом никакой нет, и следующий после вас разработчик потратит слишком много времени для того, чтобы разобраться в устройстве проекта. Например если вся суть экрана — загрузить список треков с сервера, отсортировать и показать, то вопрос показа стоит вынести во вью (сделать ему property NSArray<PRXTrack*>* tracks), вопрос сортировки оставить в контроллере, как и получение данных, оставить в контроллере. Контроллер вызывает дата менеджер, который вызывает сервер менеджер, получает данные, мэпит и возвращает обратно.
                                    0
                                    Красивее и правильней создать отдельную сущность для получения и сортировки, она будет тестируема, да, но это сделает кодовую базу сложнее, чего стоит избегать. Если экран большой и содержит в себе очень много логики — возможно да, имеет смысл озаботиться вопросом. Если приложение security-sensitive и тестирование необходимо — тоже. Нужно лишь понимать, что ничто в этом мире не бесплатно и чем больше в коде движущихся частей, тем труднее с ним работать и иногда лучше придерживаться политики, что твой код после тебя будет поддерживать джуниор, и он должен быть максимально прост для понимания и внесения изменений.
                                  +1
                                  Зачем нам вообще прогресс?
                                  А создание и расстановка всех view в коде, а затем работа со всем этим безобразием через кучу методов протокола (функционал реализованный через делегирование сам по себе плохо читаем и уже давно устарел) звучит как-то не очень оптимистично, т.к. создание и расстановка — это уже вермишель.

                                  А теперь представьте себе, что вы впервые видите проект реализованный в таком подходе. Как долго вы будете проклинать того кто придумал все это? Я вот представил, что мне предложили проект такой «исправленный и доработанный». Я бы не согласился на работу над таким чудом.
                                    0
                                    Демагогия: Вы приравняли использование IB к прогрессу (инструменты построения интерфейса существуют уже очень давно) и сослались на то, что если прогресс это хорошо, то IB тоже.

                                    Так же вы приравняли расстановку view в коде к безобразию без аргументации. Делегирование к устаревшему подходу (подходу к решению какой конкретно проблемы?) аналогично. Почему, кстати, тогда UITableView все еще на делегатах, а не на блоках? Делегирование до появления блоков использовалось для решения всех проблем обратной связи между обьектами, и не для всех оно подходило. Блоки решили этот вопрос. Использовать блоки для всего и вся такой же моветон, как использовать делегирование аналогичным образом.

                                    Создание предполагается делать в -init у view, а расстановку в -updateConstraints. Никакой вермишели, как раз наоборот: создание и расстановка четко разделены и при этом не так далеко, чтобы приходилось бегать из IB в код и обратно, и при изменении одного менять другое. А логика вся остается в VC и никак не пересекается с созданием и расстановкой вьюшек. Какие конкретно минусы в этом подходе?
                                      +1
                                      Графические инструменты для построения графического интерфейса это однозначно прогресс относительно стопроцентной работы с UI в коде. Разве нет?

                                      Да, я считаю что написание кода там где его можно не писать — это так называемая «работа ради работы». Это занимает значительно больше времени и в дальнейшем затрудняет поддержку продукта.
                                      Да, UITableView связан с вью контроллером двумя протоколами, но это скорее исключение, т.к. методов в этих двух протоколах очень большое количество.

                                      Окей, допустим, что создание и расстановка объектов в коде грамотно распределены, но это я подразумевал как само собой разумеющееся. Большое или небольшое количество кода для этого нужно — тоже очень относительные и условные понятия. На мой взгляд, код там где его может вообще не быть — это уже большое количество кода. И это как раз первый минус в таком подходе — относительно большое количество кода.
                                      Бегать из IB в код и обратно не приходится совсем просто потому что есть Assistant Editor.
                                      Второй минус в таком подходе заключается в относительно непростом сопровождении продукта, т.к. понимать интерфейс и рисовать его у себя в голове читая код это все таки сложнее, чем просто его увидеть и посмотреть взаимосвязи.

                                      Да, это все относительно, но ваш подход заранее усложняет себе вещь, а для чего? Из-за какой-то личной неприязни IB?
                                        0
                                        Смотря в какой плоскости. Если совсем абстрагироваться от IB, то UI относительно текстового интерфейса можно и нужно считать прогрессом в плоскости связанной с UX: в большинстве случаев обычному пользователю гораздо проще как-то интуитивно найти нужную кнопку в панели управления (e.g. windows), нежели какой-то там конфиг файл (e.g. linux). Т.е. новичку, особенно далекому от IT, гораздо проще освоить некое графическое представление интерфейса, нежели текстовое. Однако конфиг файлы до сих пор существуют и повсеместно используются, и как причину такого состояния дел я вижу не лень больших компаний сделать UI для своих продуктов, например баз данных, а тот (для меня) факт, что разработчики — не обычные пользователи и им гораздо проще работать с текстом, кодом, нежели искать заветные галочки в дебрях интерфейса. Да, в общем случае для этого нужно много знать и приходится много гуглить, это сильный минус, который покрывается позднее скоростью выполнения работы и лучшим контролем.

                                        Например, что проще, зайти в панель управления -> установка/удаление программ -> найти в списке нужную, нажать на удалить и появившемся wizard еще пару раз нажать на next (как аналог можно рассмотреть упрощенные пакетные менеджеры убунты), или просто вбить в терминал `sudo pacman -R packetName`? В данной ситуации скорость очевидно за вторым вариантом, а больший контроль проявляется как только вы хотите удалить программу и все ее зависимости, или же хотите наоборот оставить зависимости на месте: в интерфейсе этого просто не выведено в упрощенных пакетных менеджерах (ведь от этого страдает UX, иначе зачем изобретать велосипед и заменять популярный и мощный Synaptic в убунту) и в windows не подразумевается возможным, а через терминал `sudo pacman -Rs packetName`. Контроль в большинстве случаев остается за вторым вариантом (оспорить или доказать это утверждение весьма проблематично, однако мне оно кажется очевидным), а его минусом остается тот факт, что пользователь должен знать, что такое sudo, pacman и аргументы последнего. Однако я не считаю, что в случае с UI работа, которую человек должен затратить на то, чтобы пройти весь путь по интерфейсу до получения желаемого результата равна нулю. Я утверждаю, что программистам проще и быстрее разобраться с консольными интерфейсами и конфиг файлами, нежели искать галочки в интерфейсе, потому что для того, чтобы интерфейс удовлетворял всем «нестандартным» потребностям, с которыми неминуемо сталкиваешься при разработке ПО, его приходится изрядно перегрузить.

                                        Возвращаясь к IB, предлагаю провести аналогию и рассмотреть конкретный пример. Допустим я хочу на экране два прямоугольника размером 100х100 и 50х35, цвет каждого прямоугольника зависит от третьих факторов (например рандом), один в правом верхнем углу, другой слева слева снизу. Первый с учетом margin'ов, второй без. При этом я не хочу, чтобы при недостатке места они пересекались, более того пересекались значения координат их точек: ни одна точка первого прямоугольника не должна быть правее ни одной точки второго, аналогично для вертикали (это упрощение, а не усложнение). Уменьшаться должен, при недостатке места, первый прямоугольник. Экран этот должен быть под UINavigationController и нажатие на любой из этих прямоугольников должно вызывать пуш каких-то других экранов в зависимости от третьих факторов (т.е переход не детерминирован на этапе компиляции). Поехали. В дальнейших рассуждениях подсчет времени, затрачиваемого на каждое действие, оставляю за вами. Все максимально минималистично и просто.

                                        IB:
                                        Находим и перетаскиваем UINavigationController, идем в соответствующую вкладку и назначаем его как стартовый. Аналогично перетаскиваем UIViewController и связываем их. Ищем кастум вью, перетаскиваем два инстанса. Выделяем одну из вьюшек, кликаем по кнопке констрейнтов снизу и выставляем соответствующие констрейнты: фиксированные размеры и отступы друг от друга (с последним может возникнуть проблема, ведь если добавить еще одну вью между ними, то IB будет предлагать выставить отступы именно от нее, а не от нужного прямоугольника, ведь она будет ближайшей, и нужно будет (?) идти во вкладку инспектора и делать это там). Далее создаем segue (один или два? А можно ли сделать segue и не указывать destination?) для каждого прямоугольника, либо же IBAction в контроллере, для чего потребуется переключиться на код. Коль уже переключились, допишем еще IBOutlet'ы в контроллер и свяжем их с вьюшками. Не забудем попросить IB обновить представление вьюшек, чтобы убедиться, что они правильно расставлены.

                                        Далее уже в коде во -viewWillAppear выставляем нужные цвета прямоугольникам через оутлеты, и либо в -prepareForSegue или в методах IBAction дописываем пуш на нужный контроллер. Кажется ничего не забыл.

                                        Code only:
                                        AppDelegate.m, в application:didFinishLoading:
                                        ```
                                        UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
                                        window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[MyViewController new]];
                                        [window makeKeyAndVisible];
                                        _window = window;
                                        ```

                                        В MyViewController.m:
                                        В viewDidLoad:
                                        ```
                                        UIView *first = [UIView new];
                                        first.backgroundColor = ...;
                                        [first addGestureRecognizer:[[UITapGestureRecognizer alloc] recogrnizerWithTarget:self selector:@selector(firstTap:)]];
                                        [self.view addSubview:first];
                                        _first = first;

                                        UIView *second = [UIView new];
                                        second.backgroundColor = ...;
                                        [second addGestureRecognizer:[[UITapGestureRecognizer alloc] recogrnizerWithTarget:self selector:@selector(secondTap:)]];
                                        [self.view addSubview:second];
                                        _second = second;
                                        ```

                                        В updateConstraints:
                                        ```
                                        _first.keepTopMarginInset.equal = 0;
                                        _first.keepRightMarginInset.equal = 0;
                                        [_first keepSize:CGSizeMake(100,100) withPriority:KeepHigh];

                                        _second.keepTopOffsetTo(_first).min = 0
                                        _second.keepLeftOffsetTo(_first).min = 0;
                                        _second.keepBottomInset.equal = 0;
                                        _second.keepLeftInset.equal = 0;
                                        [_second keepSizeTo:CGSizeMake(50,35)];
                                        ```

                                        Ну и совершенно аналогично с IB в случае с IBAction код пуша соответствующего экрана. Вы действительно считаете, что вариант с IB сделать быстрее? Этот код можно спокойно печатать даже без подсказок и других инструментов, в частности автоматических import'ов и созданий ivar'ов в AppCode (то, что в xcode писать код тяжело похоже никогда не изменится) и гораздо быстрее, чем двигать мышкой по IB в поисках нужных вещей. А с подсказками, особенно fuzzysearch, когда IDE буквально все пишет за тебя — это делается еще быстрее. Плюс этот код легче поддерживать и менять, ведь все, что происходит, находится перед глазами и не скрыто в панельках IB.

                                          0
                                          Находим и перетаскиваем UINavigationController, идем в соответствующую вкладку и назначаем его как стартовый. Аналогично перетаскиваем UIViewController и связываем их. Ищем кастум вью, перетаскиваем два инстанса. Выделяем одну из вьюшек, кликаем по кнопке констрейнтов снизу и выставляем соответствующие констрейнты: фиксированные размеры и отступы друг от друга (с последним может возникнуть проблема, ведь если добавить еще одну вью между ними, то IB будет предлагать выставить отступы именно от нее, а не от нужного прямоугольника, ведь она будет ближайшей, и нужно будет (?) идти во вкладку инспектора и делать это там). Далее создаем segue (один или два? А можно ли сделать segue и не указывать destination?) для каждого прямоугольника, либо же IBAction в контроллере, для чего потребуется переключиться на код. Коль уже переключились, допишем еще IBOutlet'ы в контроллер и свяжем их с вьюшками. Не забудем попросить IB обновить представление вьюшек, чтобы убедиться, что они правильно расставлены.

                                          К сожалению, из этого абзаца ясно, что вы практически не работали с IB и Constraints.


                                          1. Констрейнты могуть быть не фиксированными, а с доп условиями: "не меньше стольки-то" или "не больше стольки-то".
                                          2. То же самое и с отступами между вьюшками (кстати, зачем вообще вьюшки? Почему нельзя, чтоб это были два UIButton без текста и залитые необходимым цветом?)
                                          3. Чтобы выставить отсупы не от ближайшего объекта а от любого объекта не нужно идти ни в какие вкладки, нужно зажать Ctrl и перетянуть констрейнт с одного объекта на другой (на любой другой).

                                          Пример, который вы описали делается практически полностью в IB. В коде только прописываем переходы по нажатию на прямоугольники на другие вью-контроллеры. И да, возможно, процесс выполнения этого примера через IB займет немного (действительно немного) больше времени, чем ваш вариант.


                                          Но есть главное "НО": когда любой другой разработчик откроет проект, в котором это все сделано через IB — он без лишних вопросов поймет что вообще происходит в этом приложении. Ваш же вариант будет понятен только вам. :(

                                            0
                                            1. Очевидно, с чего взяли? Я сказал, открываем менюшку и делаем констрейнты.
                                            2. Очевидно, с чего взяли? По поводу button — а что это изменит?
                                            3. А вот этого не знал.

                                            И я тоже самое сделал, что вы написали — я весь интерфейс сделал в IB, а цвета по условию должны рождаться из кода, следовательно нужны IBOutlet.

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

                                            Т.е я не согласен с утверждением, что любой разработчик сразу поймет проект по сториборду и частично не согласен, что не поймет код — единственная сторонняя библиотека тут это KeepLayout. Можно открыть их страницу гитхаба и один раз прочитать. Констрейнты везде одинаковые и несут один и тот же смысл. Опять же, в следствии малого обьема получается как-то не серьезно. Мы всетаки разработчики, а не индусы, это наша работа шевелить мозгами. Разница лишь в том, на сколько адекватно то, над чем тебе приходится думать, и на сколько усилия соответствуют получаемому результату. Зашарить маленькую обертку над оригинальным AutoLayout займет совсем ничего, а результат — возможность не вылазить из кода и делать интерфейс быстрее и проще, нежели двигать мышкой.
                                              +1
                                              ни разу за свою практику не видел адекватного IB

                                              Это автоматически вызывает вопрос о вашей практике. Как долго и чем вы занимались на этом поприще?

                                              не согласен с утверждением, что любой разработчик сразу поймет проект по сториборду

                                              А и не было утверждения «сразу поймет». Есть утверждение «легче чем».

                                              наша работа шевелить мозгами

                                              Подобные вызывают у меня ужас. Наша работа шевелить мозгами тогда когда это действительно нужно.
                                              Почитайте что говорил Larry Wall об основных достоинствах программистов.
                                            +1
                                            1. У вас недостаточны познания в работе с UI. Советую углубиться в тему и поискать рекомендации по его использованию этого инструмента.
                                            2. Я не призывал ни к одной из крайностей типа «весь проект в одном сторибоарде» или «каждому экрану свой сторибоард». Будьте пожалуйста внимательнее.
                                            3. Вешать UITapGestureRecognizer на UIView чтобы отследить нажатие — это вообще костыль размером с Эверест.

                                            Согласен с тем, что ваш код читается достаточно неплохо, хотя подход с тем же UITapGestureRecognizer просто ужасен, (я такую реализацию в тестовом задании принял бы за грубую ошибку, а в работе над проектом на code review требовал бы все это исправить).

                                            4. Даже если код читается легко, но при этом избыточен — такому коду не место в проекте. Код которого может не быть — это на мой взгляд избыточный код.
                                      0

                                      Автор оригинальной статьи Stan_ost, к сожалению, не может оставить комментарий лично, поэтому процитирую его сообщение:


                                      Привет! Я автор оригинальной статьи. Жалко, комментировать тут нельзя спустя три дня после публикации. Хотел ответь на вопрос, зачем вообще в первую очередь использовать Storyboard. В прошлых компаниях, где я работал, мы обходились без IB, и все были счастливы. Но вот на новой работе IB и Storyboards уже были частью проекта. Как говориться, со своим уставом в чужой монастырь не ходят, так что пришлось приспосабливаться. Отсюда и вывел такие советы.
                                    0
                                    Предпочитаю все делать в коде, более того система anchors позволяет это делать очень удобно и быстро. До этого юзал специальную либу — SnapKit. Вспоминаю создание констрейнтов в сториборде как кошмар.
                                      0
                                      Советую KeepLayout. Задача всех этих оберток — предоставить синтаксический сахар и только.

                                      В anchors нужно писать вот так
                                      ```
                                      [_myView.bottomAnchor constraintEqualToAnchor:_view.topAnchor constant:8.0].active = YES;
                                      ````

                                      в KeepLayout вот так

                                      ```
                                      _myView.keepTopInset.equal = 8;
                                      ```
                                      если я правильно понял, что делает anchors в этом коде (скорее всего нет :) ).
                                      0
                                      Всегда так делаю, кроме того, что разбиваю на много бордов. Просто, наверное, пока не доводилось работать в большой команде…
                                        0
                                        Я думаю, что раскидывать все view controller'ы по разным storyboard-файлам — это такая же крайность как и заталкивать все в один файл.
                                        Мне кажется вполне приемлемым вариантом такой подход: один логический сегмент приложения — один storyboard.
                                        К примеру если в приложении есть три вкладки, то у меня будет как минимум по одному storyboard'у на каждую из них + возможно еще что-то добавится если что-то будет логично выделить (регистрацию/настройки и т.п.).
                                          0

                                          Это все хорошо ровно до того момента, как двум разработчикам прилетят две разные задачи, обе из которых затрагивают изменения UI в рамках одного логического сегмента приложения. :)

                                            0
                                            На мой взгляд это выдуманная проблема, т.к. в этом случае это можно доверить обе задачи одному разработчику.
                                            Так если бояться малейшей вероятности возникновения конфликтов, то не долго и до паранойи. :)
                                            «Волков бояться — в лес не ходить» (с)
                                              0

                                              Проблема как раз довольно реальная, и я с ней столкнулся уже в первый свой год iOS разработки. :)


                                              Обычно с конфликтами при слиянии веток в гите — проблем не бывает. Если они в файлах с кодом — все всегда разрешается без проблем. Но только не конфликты в сторибордах. Об этом же и статья. :)

                                                +1
                                                Значит это зависит еще и подхода. Я за более чем три года с таким не столкнулся ни разу.

                                        Only users with full accounts can post comments. Log in, please.