Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Unlimited power of Data-Driven UI
Search
DAloG
April 21, 2018
Programming
700
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
240
Redux + MQTT
dalog
1
820
От задач к проблемам
dalog
1
290
Data-Driven View Controllers. Tips and Tricks
dalog
5
2k
2 years of Redux in iOS. Lessons learned
dalog
0
410
Why unidirectional architecture matter for iOS.
dalog
1
330
Mobile backend without REST
dalog
2
150
Self managed teams 101
dalog
0
200
FMVP
dalog
1
200
Other Decks in Programming
See All in Programming
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
790
JavaDoc 再入門
nagise
1
350
「エンジニアインターン、どうやって取った?」準備のリアルを語るLT会 Progate BAR
akiomatic
0
130
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
350
Datadog × OpenTelemetry 入門と実践のあいだ
kn_to_maxpno
1
160
Agentic UI
manfredsteyer
PRO
0
160
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
200
タクシーアプリ『GO』の バックエンド開発のおける AI利活用と若者のすべて
pyama86
3
2k
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
700
AIだと陥りがちなJakarta EE最新技術への移行時の落とし穴と解決策
tnagao7
0
110
技術記事、AIに書かせるか、自分で書くか? 〜それでも私が自分の手で書く理由〜 / #QiitaConference
jnchito
2
1.4k
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
5.1k
Featured
See All Featured
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
230
The Spectacular Lies of Maps
axbom
PRO
1
810
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
160
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
340
Ethics towards AI in product and experience design
skipperchong
2
310
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
1.7k
Context Engineering - Making Every Token Count
addyosmani
9
970
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Amusing Abliteration
ianozsvald
1
200
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
250
My Coaching Mixtape
mlcsv
0
150
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