![](https://habrastorage.org/getpro/habr/post_images/8cb/0be/d37/8cb0bed374078aefbbaa13a03283f6b4.png)
Этот пост является вольным переводом статьи 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, который выглядит вот так?
![](https://habrastorage.org/getpro/habr/post_images/0da/a2d/307/0daa2d307563d07fa58f203230bc2e5b.png)
С точки зрения дизайнера, все хорошо: полностью видно UI и навигацию. И это именно то, для чего Interface Builder и был создан.
Но для разработчика это несет множество проблем:
- Контроль версий: конфликты слияния сторибордов очень трудно решать, так что работа в отдельных сторибордах сделает жизнь вашей команды проще.
- Файл сториборда становится объемным и в нем сложно ориентироваться. Как часто вы случайно меняли constraint кликом мышки не в том вью-контроллере?
- Вам необходимо присваивать каждому вью-контроллеру свой storyboard ID и это может привести к ошибкам: вам нужно «хардкодить» этот ID каждый раз когда хотите использовать этот вью-контроллер в коде.
Как же связать различные сториборды в вашем проекте? Есть два способа.
Используйте ссылки на сториборды (storyboard referencing), которые появились в Xcode 7.
- Связывайте сториборды непосредственно в коде.
О первом способе вы можете почитать детальнее здесь.
Я расскажу о втором способе, так как он широко используется для сложных проектов.
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" навигационного стэка.
![](https://habrastorage.org/getpro/habr/post_images/5f3/0af/3cf/5f30af3cfde98aeb5e60ea5300454291.png)
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. Но об этом будет уже другой пост.
Комментарии (48)
Matsepura
17.10.2016 10:03А кто-нибудь замерял, на сколько увеличивается вес приложения при дроблении одного сториборда на несколько? Было бы очень интересно узнать)
И большое спасибо за статью!s_suhanov
17.10.2016 10:15+1Вот размеры каталога с проектом до дробления и после (слева — до, справа — после):
ard
17.10.2016 10:24+6Swift позволяет создать обобщенное решение, чтобы не копипастить метод создания в каждый класс:
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() } }
s_suhanov
17.10.2016 10:28Да, так красивее.
Правда у меня есть пара вью-контроллеров вложенных в навигэйшны, и в таких случаях мне нужно, чтоб метод
storyboardInstance()
возвращал неSelf?
, аUINavigationController?
freeg0r
19.10.2016 18:38Не понял совсем, какая связь между именем (типом) класса контроллера и именем storyboard, который может содержать кучу таких контроллеров, инстанс каждого из которых может понадобиться.
MeGaPk
17.10.2016 10:59+1у меня лично сделано так:
1 сториборд только с uiviewcontroller (для проверки есть ли заказы) и uinavigation.
2-3 сториборда под свои степы (поиск, процесс заказа).
И благодаря тому, что в iOS восхитительно работает горячая замена контроллеров в навигации ( [self setViewControllers:@[...] animated:YES];, ес-но все прекрасно dealloc'ируется), упрощает жизнь при разработке в 100500 раз и не надо запариваться на то, что бы, как перейти с одного контроллера на другой, если так не прописано «логикой».
vlad9486
17.10.2016 11:06+1Раньше, когда был iOS 7, Xcode 5, трава зеленее, не было никаких сторибордов. Были просто xib'ы, по одному на контроллер (и для UIView иногда). Еще тогда не понимал этой идеи — держать много экранов в одном файле. Рад что некоторые люди, как и я, вернулись к раздельным экранам.
AlexIzh
17.10.2016 11:17Сейчас xib'ы никуда не делись. Не знаю почему автор использует Storyboard на каждый экран, вместо использования xib'ов. Очень странный подход
Serg_de_Adelantado
17.10.2016 13:49+2Подход оправдывает себя, если нужно реализовать, скажем, master-detail экраны, или встроить в контроллер UIPageView через UIContainerView. Распостраненный пример: несколько панелей настроек, переключаемых через UISegmentedControl. Эти панельки не переиспользуются на других экранах приложения, и логически объединены в одну группу, потому целесообразно использовать для них один storyboard.
AlexIzh
17.10.2016 13:53В этом ключе — да, конечно вы правы. Но я не говорил, что вообще не нужно так делать, но и в большинстве случаев, это излишне. В статье про такие вещи не сказано ни слова, из чего можно сделать вывод, что отдельные сториборды так же используются вместо xib'ов в большинстве случаев.
Тут конечно же нужно смотреть по ситуации, что необходимо использовать, но никогда не прыгать из крайности в крайность и использовать лишь один инструмент для всех случаев.
dimakey
17.10.2016 11:29+1Идея сторибордов состоит в том, чтобы разместить в одном сториборде один «стори». Например, какой-то процесс из нескольких шагов (визард создания лота в каком-нибудь клоне eBay, процедура вызова такси и т.д.). В теории, это должно было облегчить работу программистов. Но на практике неудобство переиспользования одного и того же вью контроллера в одной «стори» привело к тому, что мы просто отказались от использования сторибордов. Была мысль делать отдельный сториборд для каждого вью контроллера, но стало непонятно, чем это лучше старых привычных xib.
kpower
17.10.2016 14:22Скажите, а почему тогда storyboard, а не xib?
И да, мелкий нюанс про ссылки на storyboard'ы — там помимо ограничения по Xcode, есть и по iOS (если не ошибаюсь, 8.0+) — проверьте перед использованием.s_suhanov
17.10.2016 15:59Ограничения по версии iOS (8.0+) есть и в свежем Xcode 8.
А если вы имеет ввиду использование storyboard referencing, то ограничение по версии iOS — 9.0+
aspcartman
17.10.2016 15:50А еще лучше просто не использовать сториборды вообще.
aspcartman
17.10.2016 16:27Уточню. Совсем. Никаких .storyboard, никаких .xib, никакого interface builder, никогда. Я не видел ни одного проекта за шесть лет опыта с IB, который был бы действительно хорошо написан. С .xib — да, было, но .storyboard — всегда ад. Даже если по сториборде на экран.
Давайте честно. Зачем нужны сториборды вообще? Вот альтернативный подход, критикуйте:
На каждый экран свой контроллер, и свой подкласс UIView. Весь лейаут и взаимодействие с пользователем — во вью, вся бизнесс логика — в контроллере. Контроллер пинает вью напрямую, вью — через протокол делегирования. Лейаут — через обертки аутолейаута по вкусу (KeepLayout), код в -updateContraints. Для ускорения процесса разработки — dyci или аналог (нажал хоткей — изменения в коде инжектировались и экран обновился).
Вот накипело уже. От IB одна только головная боль и толпы радостных индусов, размахивающих очередным новым способом не увязнуть в дебрях IB, продолжая использовать IB. Мыши плакали, кололись, но продолжали жрать кактус. Приходишь собеседоваться в большую серьезную компанию, а они пол года ваяют приложение и никуда оно не движется. Смотришь, код вроде бы нормальный, разработчики толковые и адекватные, а в сторибордах очередной срач, IBDesignable падает и не работает, все в каких-то страшных Segue и черт ногу сломает. Зато MVVC, IB, Swift, ага. Вам шашечки или ехать? Надоело переписывать за другими разрабами проекты. Нет, с одной стороны мне за это платят зарплаты, но с другой разобраться в этой дикой вермишели порой бывает без бутылки не возможно и нервы не резиновые.s_suhanov
17.10.2016 16:30+1Ну описаный подход как раз для того, чтоб не было "в сторибордах очередной срач, IBDesignable падает и не работает, все в каких-то страшных Segue и черт ногу сломает" :)
aspcartman
17.10.2016 16:49+1Я согласен с тем, что описанный подход улучшает ситуацию. Но это не отвечает на вопрос, который я задал: зачем нужны сториборды вообще? Зачем нужно поддерживать какую-то дополнительную сущность, при работе с которой требуется быть очень осторожным, тратить так много времени на разброску констрейнтов и атрибутов контролов мышкой, заниматься копипастой, пробрасывать IBOutlet'ы и прочая прочая?
s_suhanov
17.10.2016 17:26+1Мне кажется, что все-таки сториборды хорошо помогают в визуальном понимании текущего вью. :)
aspcartman
17.10.2016 17:33+2Это плюс, но разве он оправдывает минусы? Тем более, что всегда можно запустить симулятор и посмотреть в живую. Это не так удобно, с одной стороны, с другой стоимость сторибордов для такой простой вещи оказывается слишком велика, нужны какие-то еще плюсы, чтобы ее оправдать. А с учетом инжектов без перекомпиляции/перезапуска плюс совсем меркнет.
nepx
18.10.2016 14:36+1Бизнес-логика в контроллере? В модели же…
aspcartman
18.10.2016 14:42На первый взгляд логично, но спорно. Паттерн MVC, как его предоставляет нам Apple, к сожалению толкает разработчиков выносить логику из модели в контроллер. Вопрос тестирования начинает гореть синими пламенем в этот момент и да, в общем случае действительно лучше сопротивляться и выносить все в модель. Но у медали есть обратная сторона: в результате таких действий, если логики на самом деле мало, можно легко нарушить принцип KISS, получить большое количество сущностей и абстракций там, где необходимости в этом никакой нет, и следующий после вас разработчик потратит слишком много времени для того, чтобы разобраться в устройстве проекта. Например если вся суть экрана — загрузить список треков с сервера, отсортировать и показать, то вопрос показа стоит вынести во вью (сделать ему property NSArray<PRXTrack*>* tracks), вопрос сортировки оставить в контроллере, как и получение данных, оставить в контроллере. Контроллер вызывает дата менеджер, который вызывает сервер менеджер, получает данные, мэпит и возвращает обратно.
aspcartman
18.10.2016 14:53Красивее и правильней создать отдельную сущность для получения и сортировки, она будет тестируема, да, но это сделает кодовую базу сложнее, чего стоит избегать. Если экран большой и содержит в себе очень много логики — возможно да, имеет смысл озаботиться вопросом. Если приложение security-sensitive и тестирование необходимо — тоже. Нужно лишь понимать, что ничто в этом мире не бесплатно и чем больше в коде движущихся частей, тем труднее с ним работать и иногда лучше придерживаться политики, что твой код после тебя будет поддерживать джуниор, и он должен быть максимально прост для понимания и внесения изменений.
AKhatmullin
24.10.2016 10:44+1Зачем нам вообще прогресс?
А создание и расстановка всех view в коде, а затем работа со всем этим безобразием через кучу методов протокола (функционал реализованный через делегирование сам по себе плохо читаем и уже давно устарел) звучит как-то не очень оптимистично, т.к. создание и расстановка — это уже вермишель.
А теперь представьте себе, что вы впервые видите проект реализованный в таком подходе. Как долго вы будете проклинать того кто придумал все это? Я вот представил, что мне предложили проект такой «исправленный и доработанный». Я бы не согласился на работу над таким чудом.aspcartman
25.10.2016 00:44Демагогия: Вы приравняли использование IB к прогрессу (инструменты построения интерфейса существуют уже очень давно) и сослались на то, что если прогресс это хорошо, то IB тоже.
Так же вы приравняли расстановку view в коде к безобразию без аргументации. Делегирование к устаревшему подходу (подходу к решению какой конкретно проблемы?) аналогично. Почему, кстати, тогда UITableView все еще на делегатах, а не на блоках? Делегирование до появления блоков использовалось для решения всех проблем обратной связи между обьектами, и не для всех оно подходило. Блоки решили этот вопрос. Использовать блоки для всего и вся такой же моветон, как использовать делегирование аналогичным образом.
Создание предполагается делать в -init у view, а расстановку в -updateConstraints. Никакой вермишели, как раз наоборот: создание и расстановка четко разделены и при этом не так далеко, чтобы приходилось бегать из IB в код и обратно, и при изменении одного менять другое. А логика вся остается в VC и никак не пересекается с созданием и расстановкой вьюшек. Какие конкретно минусы в этом подходе?AKhatmullin
25.10.2016 08:50+1Графические инструменты для построения графического интерфейса это однозначно прогресс относительно стопроцентной работы с UI в коде. Разве нет?
Да, я считаю что написание кода там где его можно не писать — это так называемая «работа ради работы». Это занимает значительно больше времени и в дальнейшем затрудняет поддержку продукта.
Да, UITableView связан с вью контроллером двумя протоколами, но это скорее исключение, т.к. методов в этих двух протоколах очень большое количество.
Окей, допустим, что создание и расстановка объектов в коде грамотно распределены, но это я подразумевал как само собой разумеющееся. Большое или небольшое количество кода для этого нужно — тоже очень относительные и условные понятия. На мой взгляд, код там где его может вообще не быть — это уже большое количество кода. И это как раз первый минус в таком подходе — относительно большое количество кода.
Бегать из IB в код и обратно не приходится совсем просто потому что есть Assistant Editor.
Второй минус в таком подходе заключается в относительно непростом сопровождении продукта, т.к. понимать интерфейс и рисовать его у себя в голове читая код это все таки сложнее, чем просто его увидеть и посмотреть взаимосвязи.
Да, это все относительно, но ваш подход заранее усложняет себе вещь, а для чего? Из-за какой-то личной неприязни IB?aspcartman
25.10.2016 13:40Смотря в какой плоскости. Если совсем абстрагироваться от 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.
s_suhanov
25.10.2016 14:03Находим и перетаскиваем UINavigationController, идем в соответствующую вкладку и назначаем его как стартовый. Аналогично перетаскиваем UIViewController и связываем их. Ищем кастум вью, перетаскиваем два инстанса. Выделяем одну из вьюшек, кликаем по кнопке констрейнтов снизу и выставляем соответствующие констрейнты: фиксированные размеры и отступы друг от друга (с последним может возникнуть проблема, ведь если добавить еще одну вью между ними, то IB будет предлагать выставить отступы именно от нее, а не от нужного прямоугольника, ведь она будет ближайшей, и нужно будет (?) идти во вкладку инспектора и делать это там). Далее создаем segue (один или два? А можно ли сделать segue и не указывать destination?) для каждого прямоугольника, либо же IBAction в контроллере, для чего потребуется переключиться на код. Коль уже переключились, допишем еще IBOutlet'ы в контроллер и свяжем их с вьюшками. Не забудем попросить IB обновить представление вьюшек, чтобы убедиться, что они правильно расставлены.
К сожалению, из этого абзаца ясно, что вы практически не работали с IB и Constraints.
- Констрейнты могуть быть не фиксированными, а с доп условиями: "не меньше стольки-то" или "не больше стольки-то".
- То же самое и с отступами между вьюшками (кстати, зачем вообще вьюшки? Почему нельзя, чтоб это были два UIButton без текста и залитые необходимым цветом?)
- Чтобы выставить отсупы не от ближайшего объекта а от любого объекта не нужно идти ни в какие вкладки, нужно зажать Ctrl и перетянуть констрейнт с одного объекта на другой (на любой другой).
Пример, который вы описали делается практически полностью в IB. В коде только прописываем переходы по нажатию на прямоугольники на другие вью-контроллеры. И да, возможно, процесс выполнения этого примера через IB займет немного (действительно немного) больше времени, чем ваш вариант.
Но есть главное "НО": когда любой другой разработчик откроет проект, в котором это все сделано через IB — он без лишних вопросов поймет что вообще происходит в этом приложении. Ваш же вариант будет понятен только вам. :(
aspcartman
25.10.2016 14:171. Очевидно, с чего взяли? Я сказал, открываем менюшку и делаем констрейнты.
2. Очевидно, с чего взяли? По поводу button — а что это изменит?
3. А вот этого не знал.
И я тоже самое сделал, что вы написали — я весь интерфейс сделал в IB, а цвета по условию должны рождаться из кода, следовательно нужны IBOutlet.
С утверждением про любого другого разработчика не согласен. Если вы читали, что я писал выше, то на практике ни разу за свою практику не видел адекватного IB, чтобы можно было на него глянуть и увидеть такой же экран, который будет в рантайме. Видимо для достижения такого результата требуется затратить еще больше усилий, раз уж разработчики так не делают.
Т.е я не согласен с утверждением, что любой разработчик сразу поймет проект по сториборду и частично не согласен, что не поймет код — единственная сторонняя библиотека тут это KeepLayout. Можно открыть их страницу гитхаба и один раз прочитать. Констрейнты везде одинаковые и несут один и тот же смысл. Опять же, в следствии малого обьема получается как-то не серьезно. Мы всетаки разработчики, а не индусы, это наша работа шевелить мозгами. Разница лишь в том, на сколько адекватно то, над чем тебе приходится думать, и на сколько усилия соответствуют получаемому результату. Зашарить маленькую обертку над оригинальным AutoLayout займет совсем ничего, а результат — возможность не вылазить из кода и делать интерфейс быстрее и проще, нежели двигать мышкой.AKhatmullin
26.10.2016 10:11+1ни разу за свою практику не видел адекватного IB
Это автоматически вызывает вопрос о вашей практике. Как долго и чем вы занимались на этом поприще?
не согласен с утверждением, что любой разработчик сразу поймет проект по сториборду
А и не было утверждения «сразу поймет». Есть утверждение «легче чем».
наша работа шевелить мозгами
Подобные вызывают у меня ужас. Наша работа шевелить мозгами тогда когда это действительно нужно.
Почитайте что говорил Larry Wall об основных достоинствах программистов.
AKhatmullin
26.10.2016 10:02+11. У вас недостаточны познания в работе с UI. Советую углубиться в тему и поискать рекомендации по его использованию этого инструмента.
2. Я не призывал ни к одной из крайностей типа «весь проект в одном сторибоарде» или «каждому экрану свой сторибоард». Будьте пожалуйста внимательнее.
3. Вешать UITapGestureRecognizer на UIView чтобы отследить нажатие — это вообще костыль размером с Эверест.
Согласен с тем, что ваш код читается достаточно неплохо, хотя подход с тем же UITapGestureRecognizer просто ужасен, (я такую реализацию в тестовом задании принял бы за грубую ошибку, а в работе над проектом на code review требовал бы все это исправить).
4. Даже если код читается легко, но при этом избыточен — такому коду не место в проекте. Код которого может не быть — это на мой взгляд избыточный код.
s_suhanov
25.10.2016 09:57Автор оригинальной статьи Stan_ost, к сожалению, не может оставить комментарий лично, поэтому процитирую его сообщение:
Привет! Я автор оригинальной статьи. Жалко, комментировать тут нельзя спустя три дня после публикации. Хотел ответь на вопрос, зачем вообще в первую очередь использовать Storyboard. В прошлых компаниях, где я работал, мы обходились без IB, и все были счастливы. Но вот на новой работе IB и Storyboards уже были частью проекта. Как говориться, со своим уставом в чужой монастырь не ходят, так что пришлось приспосабливаться. Отсюда и вывел такие советы.
nepx
18.10.2016 14:34Предпочитаю все делать в коде, более того система anchors позволяет это делать очень удобно и быстро. До этого юзал специальную либу — SnapKit. Вспоминаю создание констрейнтов в сториборде как кошмар.
aspcartman
18.10.2016 14:59Советую KeepLayout. Задача всех этих оберток — предоставить синтаксический сахар и только.
В anchors нужно писать вот так
```
[_myView.bottomAnchor constraintEqualToAnchor:_view.topAnchor constant:8.0].active = YES;
````
в KeepLayout вот так
```
_myView.keepTopInset.equal = 8;
```
если я правильно понял, что делает anchors в этом коде (скорее всего нет :) ).
egormerkushev
22.10.2016 01:44Всегда так делаю, кроме того, что разбиваю на много бордов. Просто, наверное, пока не доводилось работать в большой команде…
AKhatmullin
24.10.2016 10:49Я думаю, что раскидывать все view controller'ы по разным storyboard-файлам — это такая же крайность как и заталкивать все в один файл.
Мне кажется вполне приемлемым вариантом такой подход: один логический сегмент приложения — один storyboard.
К примеру если в приложении есть три вкладки, то у меня будет как минимум по одному storyboard'у на каждую из них + возможно еще что-то добавится если что-то будет логично выделить (регистрацию/настройки и т.п.).s_suhanov
24.10.2016 10:59Это все хорошо ровно до того момента, как двум разработчикам прилетят две разные задачи, обе из которых затрагивают изменения UI в рамках одного логического сегмента приложения. :)
AKhatmullin
24.10.2016 12:36На мой взгляд это выдуманная проблема, т.к. в этом случае это можно доверить обе задачи одному разработчику.
Так если бояться малейшей вероятности возникновения конфликтов, то не долго и до паранойи. :)
«Волков бояться — в лес не ходить» (с)s_suhanov
24.10.2016 12:59Проблема как раз довольно реальная, и я с ней столкнулся уже в первый свой год iOS разработки. :)
Обычно с конфликтами при слиянии веток в гите — проблем не бывает. Если они в файлах с кодом — все всегда разрешается без проблем. Но только не конфликты в сторибордах. Об этом же и статья. :)
AKhatmullin
24.10.2016 15:08+1Значит это зависит еще и подхода. Я за более чем три года с таким не столкнулся ни разу.
Bimawa
До сих пор для нас лучший способ не работать со storyboards…
Пост красивый спасибо!
s_suhanov
Знаете, применив описанный в посте метод — не соглашусь с вами. Теперь работать стало в разы удобнее. Сториборды открываются мгновенно, распределенная разработка идет прекрасно, все изменения сливаются друг с другом без конфликтов. Я доволен. :)
Bimawa
Да, действительно, это решает кучу проблем с мерджем. Но меня убивают автолайауты, вот это таскание мышкой стороны, привязки вьюх, расстановка приоритетов, не знаю либо это мой анскилл, либо это действительно очень не удобное средство для расстановки лайаутов, и при всем при этом, тебе все же нужно залезть в код и все поправить. Потом один сторибоар для одного экрана, по мне дак попахивает костылем, одобренным Apple?
Хочу пост о том как работают люди в storybords с максимальной производительностью. А пока я буду считать что сотиробды это то что бывает, когда нет хороших дизайнеров.
s_suhanov
Это точно не ваш анскилл. Тут реально нужно очень долго и нудно этот вопрос изучать. Я иногда натыкаюсь на компании, которым нужен даже не столько разработчик, сколько специалист именно по сторибордам/лэйаутам/констреинтам. :)
По поводу костыля — не думаю, что это костыль. Один сториборд — один экран, это хорошо как по мне. Можно же и все реализации классов сложить в один файл, но никто (ну почти никто) так не делает, и костылем это совсем не считается. :)
AlexIzh
Согласен с вами, человек использует Storyboard, но ограничивается функциональностью xib. Почему бы сразу не использовать xib'ы? Тогда бы вся статья уложилась в одно предложение и было бы более хорошее решение. Просто: "Лучший способ работать со сторибордами? Не работайте с ними, используйте xib. " — вот и вся статья.
Но автор решил пойти трудным путем и использует более мощный инструмент лишь на малую его часть. По аналогии, это как взять КАМАЗ для того, что бы перевезти детскую песочную лопатку.
s_suhanov
Ну у меня есть сториборды и с несколькими экранами. :) Например тот же таб-бар предложенным образом не разные сториборды не разделить. :)
deej
В xib нет top/bottom layout guide'ов, из-за одного этого storyboard с одним экраном лучше, чем xib.
Serg_de_Adelantado
Можно попробовать библиотеку SnapKit: тот же auto layout, но в коде. Получается эдакая удобная декларативная разметка.
Bimawa
SnapKit(Swift)/Masonry(Obj-c) да это единственная адекватная либа (из всех что видел на github) позволяющая работать с autolayouts и единственное что спасает идиотский инструмент(autolayouts) от полного отказа его использования.
Но мы перешили на ADK и полностью отказались от autolayouts (кроме тех мест где меньше 2 вьюх на экране). И теперь живем очень даже не плохо. Рекомендую.