Программирование
СтрижПИ, или SwiftUI на практике
SwiftUI не чурается залезть внутрь ваших вьюх и если он узнает лежащие там компоненты, он может принимать разные решения в разных ситуациях.
Никита Прокопов в своем блоге рассказал про «необычные» особенности декларативного фреймворка SwiftUI и том, что несет в себе его использование.
Если вдруг вы еще не знаете, примерно полгода назад Apple анонсировала новый UI фреймворк — SwiftUI. Событие очень серьезное, и как iOS разработчик со стажем я тут же переключился на него — сначала попробовать, а потом уже и выпускать production приложения. Спустя полгода и два заапрувленных релиза, хочу поделиться с вами мыслями о SwiftUI.
Во-первых, то, что Apple выпустила нормальный реактивный data-driven UI фреймворк, заслуживает исключительно всяческих похвал. Правильный тренд, правильный подход, _почти_ интерактивное превью, все вовремя и к месту. Нам, iOS-программистам, чего-то такого сильно не хватало.
Во-вторых (и тут уже о сомнительном), количество синтаксического сахара, которым они там обмазываются, вызывает, скажем так, удивление. Им пришлось серьезно расширить язык, чтобы вместо условно
VStack( Image(uiImage: image), Text(title), Text(subtitle) )
можно было писать
VStack { Image(uiImage: image) Text(title) Text(subtitle) }
На что только люди не пойдут чтобы избежать споров о том где ставить запятую!
Работает это благодаря вот такому вот прекрасному интерфейсу:
То есть не только не очень понятно зачем так усложнять себе жизнь, но еще и больше 10 элементов не засунешь. Может быть создатели руководствовались правилом 7±2 и решили, что пользователи айфона не смогут всерьез осознать более 10 одновременных элеметов на экране.
То что в этом интерфейсе пропилена специальная дырка для if напрягает если честно еще больше:
Не самим фактом дырки, а вопросом, где же собственно дырки под все остальное?
(на самом деле, если хочется больше 10 элементов, есть целый специальный компонент — ForEach, который — уберите детей от экрана! — принимает список)
(этот курьез и необходимость вводить opaque types заставляет задуматься, насколько сильно нам вообще нужна статическая типизация и какой ценой. Конечно, сторонники статической типизации скажут, что это просто типизация неправильная, вот была бы правильная и проблем бы не было — что, в общем-то, универсальный аргумент, удобный в любой ситуации)
Ладно. Допустим они хотели, чтобы код красиво выглядел на презентации. Но логику надо соблюдать? Например, вот тут на мой взгляд, смешались макароны и стрекозы:
VStack(alignment: .leading) { Text("abc") .font(.subheadline) .padding(.all) }
.font() устанавливает свойство (шрифт), а .padding() заворачивает Text view в Padding view, возвращая новый тип view. То есть почему не сделать однообразно? Свойства — методами, контейнеры замыканиями?
VStack { Padding { Text("abc") .font(.subheadline) } }.alignment(.leading)
Есть и совсем странные места, например, NavigationView берет заголовок не из собственного property, а из первого лежащего в нем ребенка!
NavigationView { List {...} .navigationBarTitle(Text("Rooms")) }
Но это все конечно цветочки. Главный консерн у меня по поводу того, сколько всего SwiftUI делает сам, «магически». Например, можно написать:
Text("abc").padding()
и все. Какой именно паддинг? А какой-то дефолтный. Еще и зависящий от ситуации. Хуже того, можно вообще ничего не писать:
HStack { Text("★★★★★") Text("Avocado Toast).font(.title) ... }
И все равно обнаружить дырку вот тут:
Процитирую чувака со сцены:
SwiftUI didn’t slam all of the stack’s children against each other. It left some space between these two because Adaptive Spacing™ is in effect.
То есть SwiftUI сам решил что в этом месте нужна дырка и добавил ее. Как он это решил? На основании чего? Вы скажете «просто дефолт», но все сложнее! Насколько я понимаю, SwiftUI не чурается залезть внутрь ваших вьюх и если он узнает лежащие там компоненты, он может принимать разные решения в разных ситуациях. Например, отступить не от bounding box, а от text baseline, если он у вас где-то в глубине есть:
Или если написать
List { Button { Text("Add room") } }
То кнопка будет черненькая:
А если теперь поменять стиль списка (не кнопки!):
List { Button { Text("Add room") } }.listStyle(.grouped)
То уже синенькая!
А консерн собственно в следующем. Как человек, травмированный вебом в не самые красивые его времена, я очень настороженно отношусь к разного рода дефолтам и «умному» поведению.
Во-первых, на дефолты нельзя полагаться, потому что они будут меняться. Пока ты изловчился сделать на сегодняшних, Apple выкатывает завтрашние и они становятся еще умнее и перекрашивают кнопки, втыкают больше паддингов, ставят контролы с другой стороны и т.п. Помните CSS reset? Может показаться, что в случае с браузерами это проблема исключительно потому, что браузеров много и они не могли договориться. SwiftUI же один, чего тут волноваться? Очень просто: во-первых, устройств у Apple много и на всех дефолты разные. А во-вторых, через два года Apple решит, что пора обновлять свой design language, и выкатит другие дефолты и все приложения поплывуууут в дальнее плавание.
Вторая причина в том, что с умным поведением нужно бороться, что тоже очень неприятное занятие. Я прекрасно помню бессонные ночи, когда я (в вебе) изо всех сил пытался убрать какие-то левые зазоры под картинками, выровнять текст с иконкой ровно как тебе нужно, пытался убить не пойми откуда взявшийся вложенный скролл, победить наезжающие друг на друга float: left иллюстрации и подобное. Потому что ситуация ровно такая же: веб предлагает набор высокоуровневых дефолтов и «умной» логики лайаута, которые иногда работают, а иногда как кость поперек горла, вытаскиваются только хирургическим путем.
И третья причина в том, что с таким подходом разработка никогда не завершится. Ты не знаешь, сколько еще «умных» дефолтов ты пропустил и как они могут потенциально испортить тебе жизнь, сейчас или в будущем. Обнаружить, что у Васи на древнем третьем айпаде и вчера вышедшей новой iOS (и только на такой комбинации) «умный» SwiftUI показывает дырку в 30 пикселей вместо 20 и из-за этого едет половина верстки, бегать за такими проблемами — занятие для молодых и сильных.
Так что с тех под я предпочитаю вещи явные, прямые и тупые, как кусок водопроводной трубы: если я забыл воткнуть зазор между картинкой и текстом, то я сам дурак и не будет никакого зазора. Если я воткнул зазор в 20 пикселей, он и останется в 20 пикселей, что бы ни случилось. Более того, об этом в коде будет явно написано буквами.
Да, такой код не будет выглядеть так красиво со сцены Steve Jobs Theater. Зато он будет делать ровно то, что в нем написано, его можно понять, предсказать, найти, разобраться и поменять. Потому что ДОБАВЛЯТЬ простые и предсказуемые компоненты просто. А вот ОТМЕНЯТЬ чужую умную непрозрачную логику тяжело. Потому что она чужая, умная и непрозрачная.
P.S. Disclaimer: People being people, многим важно не только ЧТО написано, но и кто это пишет. Некоторые не готовы слушать аргументацию человека, который не программирует на Swift минимум пять лет без отпуска. Некоторые игнорируют любую критику, автор которой не может похвастаться собственноручно задизайненым языком или (хотя бы) полноценным UI-фреймворком. Поэтому во вступлении я немножко слукавил. Конечно я никой не iOS-разработчик, я даже на Swift (языке) никогда не писал и слабо представляю, что там стоит за атмосфера. SwiftUI изучал только по статьям и презентациям. Я даже попробовать его не могу, потому что он требует Каталины, а на ней не работают никакие программы. Но зато я давно и плотно интересуюсь темой UI-фреймворков, да и программирую не первый день, так что верить мне можно.
P.P.S. Верить никому нельзя.