Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Unlimited power of Data-Driven UI
Search
DAloG
April 21, 2018
Programming
710
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Unlimited power of Data-Driven UI
DAloG
April 21, 2018
More Decks by DAloG
See All by DAloG
State normalization (RU)
dalog
0
250
Redux + MQTT
dalog
1
830
От задач к проблемам
dalog
1
290
Data-Driven View Controllers. Tips and Tricks
dalog
5
2k
2 years of Redux in iOS. Lessons learned
dalog
0
430
Why unidirectional architecture matter for iOS.
dalog
1
340
Mobile backend without REST
dalog
2
160
Self managed teams 101
dalog
0
210
FMVP
dalog
1
210
Other Decks in Programming
See All in Programming
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
200
テストを司るデーモンに会いに行く 〜隔離した仮想マシンでテストを通すまで〜
h1d3mun3
1
550
巨大モノリシックアプリ モダン化大作戦
ktcryomm
1
1.1k
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
230
iOSDC Japan 2026 - Swiftで作って学ぼう!データベース自作入門
kaseken
2
270
XHTMLが残したもの
yosuke_furukawa
PRO
2
830
What We Talk About When We Talk About XP
m_seki
2
670
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
640
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
8
5.8k
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
7
3k
WebRTC映像をAirPlayに対応させる挑戦.pdf
monolithic_adam
0
290
AGENTS.md Is Not Enough:Build Skills, Don't Download Them
lx_t
0
130
Featured
See All Featured
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
The Cost Of JavaScript in 2023
addyosmani
55
10k
Discover your Explorer Soul
emna__ayadi
2
1.3k
Automating Front-end Workflow
addyosmani
1369
210k
Building Applications with DynamoDB
mza
96
7.2k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
Believing is Seeing
oripsolob
1
220
Embracing the Ebb and Flow
colly
88
5.2k
WCS-LA-2024
lcolladotor
0
830
Unsuck your backbone
ammeep
672
58k
The Limits of Empathy - UXLibs8
cassininazir
1
680
Transcript
Unlimited power of data-driven UI by @DAlooG // 2018 @
Mobius conf
О себе — инженер в Sigma Software — 7 лет
мобильной разработки — 100% запуск чужих продуктов by @DAlooG // 2018 @ Mobius conf 2
О чем поговорим? — про проблемы разработки UI — про
однонаправленный поток данных — про тестирование UI — про анимации by @DAlooG // 2018 @ Mobius conf 3
Проблемы разработки UI by @DAlooG // 2018 @ Mobius conf
4
Моё обычное приложение — 80% бюджета уйдет на UI (дизайн,
тестирование, разработка) — 100% приложений изменят свой UI после релиза — новый функционал всегда влияет на уже готовый UI — тестирование UI ограничено возможностями уже написанной системы by @DAlooG // 2018 @ Mobius conf 5
UI это итеративный процесс by @DAlooG // 2018 @ Mobius
conf 6
Нужно много дешевых итераций by @DAlooG // 2018 @ Mobius
conf 7
Идеальный код UI — дешево тестировать — дешево добавлять новый
функционал — дешево удалять функционал — дешево двигать функционал между экранами by @DAlooG // 2018 @ Mobius conf 8
Как снизить стоимость UI разработки? — быстрые итерации — масштабировать
по людям — не трогать то что работает by @DAlooG // 2018 @ Mobius conf 9
Почему этого трудно добиться в iOS? — системные зависимости в
UI слое — жизненный цикл UIKit — UI в конце цикла поставки — зависимость от реализации back-end by @DAlooG // 2018 @ Mobius conf 10
by @DAlooG // 2018 @ Mobius conf 11
Попытка №1: MVVM + FRP — выносим логику из VC
— держим ссылку на VM в VC — слушаем изменения VM by @DAlooG // 2018 @ Mobius conf 12
by @DAlooG // 2018 @ Mobius conf 13
Проблемы MVVM — зависимость от порядка изменения полей — накопление
ошибки переходных состояний — реактивная сложность — жизненный цикл UI by @DAlooG // 2018 @ Mobius conf 14
Попытка №2: Presenter — внешний компонент у View — посылает
команды внутрь View — принимает команды из View — знает про жизненный цикл View by @DAlooG // 2018 @ Mobius conf 15
by @DAlooG // 2018 @ Mobius conf 16
Попытка №2: шаблон Presenter protocol View { func enableLoginAction() func
disableLoginAction() func beginUserLoading() ... } by @DAlooG // 2018 @ Mobius conf 17
class Presenter { let view: View let network: NetworkService func
login() { view.disableLoginAction() view.beginUserLoading() network.login().onComplete { view.enableLoginAction() view.endUserLoading() } } } by @DAlooG // 2018 @ Mobius conf 18
class Presenter { let view: View let network: NetworkService func
login() { view.disableLoginAction() view.beginUserLoading() network.login().onComplete { view.enableLoginAction() view.endUserLoading() } } } by @DAlooG // 2018 @ Mobius conf 19
Недостатки Presenter: — зависимость от порядка операция — зависимость от
жизненного цикла — иногда зависимость от предыдущих операций — внутреннее состояние ожидания ввода by @DAlooG // 2018 @ Mobius conf 20
Однонаправленный поток данных by @DAlooG // 2018 @ Mobius conf
21
Попытка №3: Props — внутренний компонент у View — полностью
изолирован — просто данные — не несет контекста рендеринга by @DAlooG // 2018 @ Mobius conf 22
by @DAlooG // 2018 @ Mobius conf 23
class ViewController { struct Props { let useraname: Field let
password: Field let login: LoginAction } render(props: Props) { ... } } by @DAlooG // 2018 @ Mobius conf 24
struct ViewController.Props.Field { let value: String let update: CommandWith<String> }
enum ViewController.Props.LoginAction { case possible(Command) case inProgress case disabled } by @DAlooG // 2018 @ Mobius conf 25
Props это внутренний компонент — меняется только вместе с UI
— дублирование кода — не использует доменный словарь — рендерится только одним View by @DAlooG // 2018 @ Mobius conf 26
Props не связан с другими слоями — способен противостоять огромным
изменениям — позволяет итерировать дизайн без логики — не требует вложений в проектирование by @DAlooG // 2018 @ Mobius conf 27
Props не обладает состоянием — семантика значения а не ссылки
— неизменяемые значения — композиция — поддерживает enum by @DAlooG // 2018 @ Mobius conf 28
View это рендеринг потока Props by @DAlooG // 2018 @
Mobius conf 29
Обратная связь: Command by @DAlooG // 2018 @ Mobius conf
30
Что такое Command? final class CommandWith<T> { private let action:
(T) -> () func perform(with value: T) { action(value) } } by @DAlooG // 2018 @ Mobius conf 31
Command это — замыкание 2.0 — гарантирует однонаправленность — помогает
в отладке by @DAlooG // 2018 @ Mobius conf 32
by @DAlooG // 2018 @ Mobius conf 33
by @DAlooG // 2018 @ Mobius conf 34
Хорошие Props это — гарантия отсутствия невозможных состояний — краткое
описание возможностей View — формат для хранения и передачи представления — однозначное описание UI by @DAlooG // 2018 @ Mobius conf 35
Как хорошо рендерить Props? func render(props: Props) { self.lablel.text =
props.title ... } by @DAlooG // 2018 @ Mobius conf 36
Как хорошо рендерить Props? func render(props: Props) { self.props =
props self.tableView.reloadData() } by @DAlooG // 2018 @ Mobius conf 37
Как хорошо рендерить Props? func render(props: Props) { self.props =
props self.view.setNeedsLayout() } by @DAlooG // 2018 @ Mobius conf 38
Как выбрать ячейку в таблице? struct ListViewController.Props { let items:
[Item] struct Item { let name: String let select: Command } } by @DAlooG // 2018 @ Mobius conf 39
Про тестирование UI by @DAlooG // 2018 @ Mobius conf
40
Как писать snapshot1 тесты? func testSomeState() { let props =
.init(...) controller.render(props: props) verify(controller, for: Device.iPhoneX.portrait) verify(controller, for: Device.iPhoneX.landscapeLeft) verify(controller, for: Device.iPadPro9.portrait.oneThird) } 1 https://github.com/AndriiDoroshko/SnappyShrimp by @DAlooG // 2018 @ Mobius conf 41
Использование в реальном2 проекте: 2 https://github.com/aol-public/OneMobileSDK-controls-ios/tree/master/SnapshotTests by @DAlooG // 2018
@ Mobius conf 42
UI storybooks — очень быстрые итерации — исследовательское тестирование —
проверка граничных условий верстки by @DAlooG // 2018 @ Mobius conf 43
by @DAlooG // 2018 @ Mobius conf 44
import Moscapsule let mqttClient: MQTTClient = { let mqttConfig =
MQTTConfig( clientId: "iOS Application", host: "127.0.0.1", port: 1883, keepAlive: 60) return MQTT.newConnection(mqttConfig) }() if let data = try? JSONEncoder().encode(self.props) { mqttClient.publish(data, topic: "CardListViewController") } by @DAlooG // 2018 @ Mobius conf 45
by @DAlooG // 2018 @ Mobius conf 46
mqttClient.subscribe("CardListViewController/setProps", qos: 0) mqttCommands.insert(CommandWith { message in guard message.topic ==
"CardListViewController/setProps" else { return } guard let payload = message.payload else { return } do { self.props = try JSONDecoder().decode(Props.self, from: payload) } catch { print(error) } }.dispatched(on: .main)) by @DAlooG // 2018 @ Mobius conf 47
Про анимацию Props by @DAlooG // 2018 @ Mobius conf
48
by @DAlooG // 2018 @ Mobius conf 49
by @DAlooG // 2018 @ Mobius conf 50
func addAnimation(view: UIView, keyPath: String, onComplete: @escaping () -> ())
{ let animation = CABasicAnimation(keyPath: keyPath) animation.duration = animationsDuration animation.delegate = AnimationDelegate(didStop: { _, completed in guard completed else { return } onComplete() }) view.layer.add(animation, forKey: keyPath) } by @DAlooG // 2018 @ Mobius conf 51
Как анимировать позицию? addAnimation(view: sideBarView, keyPath: "position") { self.sideBarView.isHidden =
true } sideBarBottomConstraint.constant = { guard #available(iOS 11, *) else { return view.frame.height - sideBarView.frame.height } return view.frame.height - sideBarView.frame.height - view.safeAreaInsets.top }() sideBarVisibleConstraint.isActive = false sideBarInvisibleConstraint.isActive = true sideBarBottomConstraint.isActive = true by @DAlooG // 2018 @ Mobius conf 52
Как анимировать прозрачность? addAnimation(view: shadowView, keyPath: "opacity") { self.shadowView.isHidden =
true } shadowView.alpha = 0 by @DAlooG // 2018 @ Mobius conf 53
Как применять Props во время анимации func renderShadowView() { switch
(currentUIProps.controlsViewHidden, nextUIProps.controlsViewHidden) { case (false, true): hideShadowView() case (true, false): showShadowView() default: guard shadowView.layer.animationKeys() == nil else { return } shadowView.isHidden = nextUIProps.controlsViewHidden } } by @DAlooG // 2018 @ Mobius conf 54
А теперь всё вместе3 if nextUIProps.bottomItemsHidden { addAnimation(view: seekerView, keyPath:
"position") { self.seekerView.isHidden = true self.seekerView.alpha = 0 setupSeekerView() } seekerToSafeAreaConstraint.isActive = false bottomItemsSeekerConstraint.isActive = true bottomItemsVisibleConstraint.isActive = false bottomItemsInvisibleConstraint.isActive = false bottomItemsAndSeekerAnimatedConstraint.isActive = true } else { addAnimation(view: seekerView, keyPath: "opacity", onComplete: setupSeekerView) if currentUIProps.bottomItemsHidden { addAnimation(view: seekerView, keyPath: "position", onComplete: setupSeekerView) } bottomItemsAndSeekerAnimatedConstraint.isActive = false seekerToSafeAreaConstraint.isActive = false bottomItemsSeekerConstraint.isActive = true seekerView.alpha = 0 } 3 https://github.com/aol-public/OneMobileSDK-controls-ios/blob/master/PlayerControls/ sources/DefaultControlsViewController.swift by @DAlooG // 2018 @ Mobius conf 55
Сложные Props struct A { let b: enum B {
case c(enum C { case d(struct D { ... by @DAlooG // 2018 @ Mobius conf 56
Enum и вложенные значения enum ViewController.Props.LoginAction { case possible(Command) case
inProgress case disabled } func render(props: Props.LoginAction) { self.loginButton.isEnabled = ??? self.loadingIndicator.isHidden = ??? } by @DAlooG // 2018 @ Mobius conf 57
Призмы!4 extension Props.LoginAction { var possible: Command? { guard case
let .possible(command) = self else { return nil } return command } var isInProgress: Bool { guard case .inProgress = self else { return false } return true } var isDisabled: Bool { guard case .disabled = self else { return false } return true } 4 https://medium.com/flawless-app-stories/enums-and-sourcery-5da57cda473b by @DAlooG // 2018 @ Mobius conf 58
Enum и вложенные значения enum ViewController.Props.LoginAction { case possible(Command) case
inProgress case disabled } func render(props: Props.LoginAction) { self.loginButton.isEnabled = ??? self.loadingIndicator.isHidden = ??? } by @DAlooG // 2018 @ Mobius conf 59
Enum и вложенные значения enum ViewController.Props.LoginAction { case possible(Command) case
inProgress case disabled } func render(props: Props.LoginAction) { self.loginButton.isEnabled = props.possible != nil self.loadingIndicator.isHidden = props.isInProgress == false } by @DAlooG // 2018 @ Mobius conf 60
Вопросы? — Twitter: @DAlooG — Email:
[email protected]
by @DAlooG //
2018 @ Mobius conf 61