Комментарии 48
До сих пор для нас лучший способ не работать со storyboards…
Пост красивый спасибо!
Да, действительно, это решает кучу проблем с мерджем. Но меня убивают автолайауты, вот это таскание мышкой стороны, привязки вьюх, расстановка приоритетов, не знаю либо это мой анскилл, либо это действительно очень не удобное средство для расстановки лайаутов, и при всем при этом, тебе все же нужно залезть в код и все поправить. Потом один сторибоар для одного экрана, по мне дак попахивает костылем, одобренным Apple?
Хочу пост о том как работают люди в storybords с максимальной производительностью. А пока я буду считать что сотиробды это то что бывает, когда нет хороших дизайнеров.
По поводу костыля — не думаю, что это костыль. Один сториборд — один экран, это хорошо как по мне. Можно же и все реализации классов сложить в один файл, но никто (ну почти никто) так не делает, и костылем это совсем не считается. :)
Потом один сторибоар для одного экрана, по мне дак попахивает костылем
Согласен с вами, человек использует Storyboard, но ограничивается функциональностью xib. Почему бы сразу не использовать xib'ы? Тогда бы вся статья уложилась в одно предложение и было бы более хорошее решение. Просто: "Лучший способ работать со сторибордами? Не работайте с ними, используйте xib. " — вот и вся статья.
Но автор решил пойти трудным путем и использует более мощный инструмент лишь на малую его часть. По аналогии, это как взять КАМАЗ для того, что бы перевезти детскую песочную лопатку.
Можно попробовать библиотеку SnapKit: тот же auto layout, но в коде.
SnapKit(Swift)/Masonry(Obj-c) да это единственная адекватная либа (из всех что видел на github) позволяющая работать с autolayouts и единственное что спасает идиотский инструмент(autolayouts) от полного отказа его использования.
Но мы перешили на ADK и полностью отказались от autolayouts (кроме тех мест где меньше 2 вьюх на экране). И теперь живем очень даже не плохо. Рекомендую.
И большое спасибо за статью!
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()
}
}
Да, так красивее.
Правда у меня есть пара вью-контроллеров вложенных в навигэйшны, и в таких случаях мне нужно, чтоб метод storyboardInstance()
возвращал не Self?
, а UINavigationController?
1 сториборд только с uiviewcontroller (для проверки есть ли заказы) и uinavigation.
2-3 сториборда под свои степы (поиск, процесс заказа).
И благодаря тому, что в iOS восхитительно работает горячая замена контроллеров в навигации ( [self setViewControllers:@[...] animated:YES];, ес-но все прекрасно dealloc'ируется), упрощает жизнь при разработке в 100500 раз и не надо запариваться на то, что бы, как перейти с одного контроллера на другой, если так не прописано «логикой».
Сейчас xib'ы никуда не делись. Не знаю почему автор использует Storyboard на каждый экран, вместо использования xib'ов. Очень странный подход
В этом ключе — да, конечно вы правы. Но я не говорил, что вообще не нужно так делать, но и в большинстве случаев, это излишне. В статье про такие вещи не сказано ни слова, из чего можно сделать вывод, что отдельные сториборды так же используются вместо xib'ов в большинстве случаев.
Тут конечно же нужно смотреть по ситуации, что необходимо использовать, но никогда не прыгать из крайности в крайность и использовать лишь один инструмент для всех случаев.
И да, мелкий нюанс про ссылки на storyboard'ы — там помимо ограничения по Xcode, есть и по iOS (если не ошибаюсь, 8.0+) — проверьте перед использованием.
Давайте честно. Зачем нужны сториборды вообще? Вот альтернативный подход, критикуйте:
На каждый экран свой контроллер, и свой подкласс UIView. Весь лейаут и взаимодействие с пользователем — во вью, вся бизнесс логика — в контроллере. Контроллер пинает вью напрямую, вью — через протокол делегирования. Лейаут — через обертки аутолейаута по вкусу (KeepLayout), код в -updateContraints. Для ускорения процесса разработки — dyci или аналог (нажал хоткей — изменения в коде инжектировались и экран обновился).
Вот накипело уже. От IB одна только головная боль и толпы радостных индусов, размахивающих очередным новым способом не увязнуть в дебрях IB, продолжая использовать IB. Мыши плакали, кололись, но продолжали жрать кактус. Приходишь собеседоваться в большую серьезную компанию, а они пол года ваяют приложение и никуда оно не движется. Смотришь, код вроде бы нормальный, разработчики толковые и адекватные, а в сторибордах очередной срач, IBDesignable падает и не работает, все в каких-то страшных Segue и черт ногу сломает. Зато MVVC, IB, Swift, ага. Вам шашечки или ехать? Надоело переписывать за другими разрабами проекты. Нет, с одной стороны мне за это платят зарплаты, но с другой разобраться в этой дикой вермишели порой бывает без бутылки не возможно и нервы не резиновые.
Ну описаный подход как раз для того, чтоб не было "в сторибордах очередной срач, IBDesignable падает и не работает, все в каких-то страшных Segue и черт ногу сломает" :)
Мне кажется, что все-таки сториборды хорошо помогают в визуальном понимании текущего вью. :)
А создание и расстановка всех view в коде, а затем работа со всем этим безобразием через кучу методов протокола (функционал реализованный через делегирование сам по себе плохо читаем и уже давно устарел) звучит как-то не очень оптимистично, т.к. создание и расстановка — это уже вермишель.
А теперь представьте себе, что вы впервые видите проект реализованный в таком подходе. Как долго вы будете проклинать того кто придумал все это? Я вот представил, что мне предложили проект такой «исправленный и доработанный». Я бы не согласился на работу над таким чудом.
Так же вы приравняли расстановку view в коде к безобразию без аргументации. Делегирование к устаревшему подходу (подходу к решению какой конкретно проблемы?) аналогично. Почему, кстати, тогда UITableView все еще на делегатах, а не на блоках? Делегирование до появления блоков использовалось для решения всех проблем обратной связи между обьектами, и не для всех оно подходило. Блоки решили этот вопрос. Использовать блоки для всего и вся такой же моветон, как использовать делегирование аналогичным образом.
Создание предполагается делать в -init у view, а расстановку в -updateConstraints. Никакой вермишели, как раз наоборот: создание и расстановка четко разделены и при этом не так далеко, чтобы приходилось бегать из IB в код и обратно, и при изменении одного менять другое. А логика вся остается в VC и никак не пересекается с созданием и расстановкой вьюшек. Какие конкретно минусы в этом подходе?
Да, я считаю что написание кода там где его можно не писать — это так называемая «работа ради работы». Это занимает значительно больше времени и в дальнейшем затрудняет поддержку продукта.
Да, UITableView связан с вью контроллером двумя протоколами, но это скорее исключение, т.к. методов в этих двух протоколах очень большое количество.
Окей, допустим, что создание и расстановка объектов в коде грамотно распределены, но это я подразумевал как само собой разумеющееся. Большое или небольшое количество кода для этого нужно — тоже очень относительные и условные понятия. На мой взгляд, код там где его может вообще не быть — это уже большое количество кода. И это как раз первый минус в таком подходе — относительно большое количество кода.
Бегать из IB в код и обратно не приходится совсем просто потому что есть Assistant Editor.
Второй минус в таком подходе заключается в относительно непростом сопровождении продукта, т.к. понимать интерфейс и рисовать его у себя в голове читая код это все таки сложнее, чем просто его увидеть и посмотреть взаимосвязи.
Да, это все относительно, но ваш подход заранее усложняет себе вещь, а для чего? Из-за какой-то личной неприязни IB?
Например, что проще, зайти в панель управления -> установка/удаление программ -> найти в списке нужную, нажать на удалить и появившемся 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.
Находим и перетаскиваем UINavigationController, идем в соответствующую вкладку и назначаем его как стартовый. Аналогично перетаскиваем UIViewController и связываем их. Ищем кастум вью, перетаскиваем два инстанса. Выделяем одну из вьюшек, кликаем по кнопке констрейнтов снизу и выставляем соответствующие констрейнты: фиксированные размеры и отступы друг от друга (с последним может возникнуть проблема, ведь если добавить еще одну вью между ними, то IB будет предлагать выставить отступы именно от нее, а не от нужного прямоугольника, ведь она будет ближайшей, и нужно будет (?) идти во вкладку инспектора и делать это там). Далее создаем segue (один или два? А можно ли сделать segue и не указывать destination?) для каждого прямоугольника, либо же IBAction в контроллере, для чего потребуется переключиться на код. Коль уже переключились, допишем еще IBOutlet'ы в контроллер и свяжем их с вьюшками. Не забудем попросить IB обновить представление вьюшек, чтобы убедиться, что они правильно расставлены.
К сожалению, из этого абзаца ясно, что вы практически не работали с IB и Constraints.
- Констрейнты могуть быть не фиксированными, а с доп условиями: "не меньше стольки-то" или "не больше стольки-то".
- То же самое и с отступами между вьюшками (кстати, зачем вообще вьюшки? Почему нельзя, чтоб это были два UIButton без текста и залитые необходимым цветом?)
- Чтобы выставить отсупы не от ближайшего объекта а от любого объекта не нужно идти ни в какие вкладки, нужно зажать Ctrl и перетянуть констрейнт с одного объекта на другой (на любой другой).
Пример, который вы описали делается практически полностью в IB. В коде только прописываем переходы по нажатию на прямоугольники на другие вью-контроллеры. И да, возможно, процесс выполнения этого примера через IB займет немного (действительно немного) больше времени, чем ваш вариант.
Но есть главное "НО": когда любой другой разработчик откроет проект, в котором это все сделано через IB — он без лишних вопросов поймет что вообще происходит в этом приложении. Ваш же вариант будет понятен только вам. :(
2. Очевидно, с чего взяли? По поводу button — а что это изменит?
3. А вот этого не знал.
И я тоже самое сделал, что вы написали — я весь интерфейс сделал в IB, а цвета по условию должны рождаться из кода, следовательно нужны IBOutlet.
С утверждением про любого другого разработчика не согласен. Если вы читали, что я писал выше, то на практике ни разу за свою практику не видел адекватного IB, чтобы можно было на него глянуть и увидеть такой же экран, который будет в рантайме. Видимо для достижения такого результата требуется затратить еще больше усилий, раз уж разработчики так не делают.
Т.е я не согласен с утверждением, что любой разработчик сразу поймет проект по сториборду и частично не согласен, что не поймет код — единственная сторонняя библиотека тут это KeepLayout. Можно открыть их страницу гитхаба и один раз прочитать. Констрейнты везде одинаковые и несут один и тот же смысл. Опять же, в следствии малого обьема получается как-то не серьезно. Мы всетаки разработчики, а не индусы, это наша работа шевелить мозгами. Разница лишь в том, на сколько адекватно то, над чем тебе приходится думать, и на сколько усилия соответствуют получаемому результату. Зашарить маленькую обертку над оригинальным AutoLayout займет совсем ничего, а результат — возможность не вылазить из кода и делать интерфейс быстрее и проще, нежели двигать мышкой.
ни разу за свою практику не видел адекватного IB
Это автоматически вызывает вопрос о вашей практике. Как долго и чем вы занимались на этом поприще?
не согласен с утверждением, что любой разработчик сразу поймет проект по сториборду
А и не было утверждения «сразу поймет». Есть утверждение «легче чем».
наша работа шевелить мозгами
Подобные вызывают у меня ужас. Наша работа шевелить мозгами тогда когда это действительно нужно.
Почитайте что говорил Larry Wall об основных достоинствах программистов.
2. Я не призывал ни к одной из крайностей типа «весь проект в одном сторибоарде» или «каждому экрану свой сторибоард». Будьте пожалуйста внимательнее.
3. Вешать UITapGestureRecognizer на UIView чтобы отследить нажатие — это вообще костыль размером с Эверест.
Согласен с тем, что ваш код читается достаточно неплохо, хотя подход с тем же UITapGestureRecognizer просто ужасен, (я такую реализацию в тестовом задании принял бы за грубую ошибку, а в работе над проектом на code review требовал бы все это исправить).
4. Даже если код читается легко, но при этом избыточен — такому коду не место в проекте. Код которого может не быть — это на мой взгляд избыточный код.
Автор оригинальной статьи Stan_ost, к сожалению, не может оставить комментарий лично, поэтому процитирую его сообщение:
Привет! Я автор оригинальной статьи. Жалко, комментировать тут нельзя спустя три дня после публикации. Хотел ответь на вопрос, зачем вообще в первую очередь использовать Storyboard. В прошлых компаниях, где я работал, мы обходились без IB, и все были счастливы. Но вот на новой работе IB и Storyboards уже были частью проекта. Как говориться, со своим уставом в чужой монастырь не ходят, так что пришлось приспосабливаться. Отсюда и вывел такие советы.
В anchors нужно писать вот так
```
[_myView.bottomAnchor constraintEqualToAnchor:_view.topAnchor constant:8.0].active = YES;
````
в KeepLayout вот так
```
_myView.keepTopInset.equal = 8;
```
если я правильно понял, что делает anchors в этом коде (скорее всего нет :) ).
Мне кажется вполне приемлемым вариантом такой подход: один логический сегмент приложения — один storyboard.
К примеру если в приложении есть три вкладки, то у меня будет как минимум по одному storyboard'у на каждую из них + возможно еще что-то добавится если что-то будет логично выделить (регистрацию/настройки и т.п.).
Это все хорошо ровно до того момента, как двум разработчикам прилетят две разные задачи, обе из которых затрагивают изменения UI в рамках одного логического сегмента приложения. :)
Так если бояться малейшей вероятности возникновения конфликтов, то не долго и до паранойи. :)
«Волков бояться — в лес не ходить» (с)
Проблема как раз довольно реальная, и я с ней столкнулся уже в первый свой год iOS разработки. :)
Обычно с конфликтами при слиянии веток в гите — проблем не бывает. Если они в файлах с кодом — все всегда разрешается без проблем. Но только не конфликты в сторибордах. Об этом же и статья. :)
Xcode: наверное, лучший способ работы со сторибордами