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
「なぜそう決めたのか」を残し続ける仕組み ― Notion AI カスタムエージェント × Slack連携による設計判断の自動記録 - NIKKEI Tech Talk #47
niftycorp
PRO
0
230
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
280
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
160
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
13
6.2k
AIだと陥りがちなJakarta EE最新技術への移行時の落とし穴と解決策
tnagao7
0
120
なぜ型を書くのか? TSKaigi2026で改めて考える #tskaigi_smarthr
kajitack
0
140
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
920
AI時代のUIはどこへ行く?その2!
yusukebe
22
7.5k
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
610
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
280
JavaDoc 再入門
nagise
1
410
AIで効率化できた業務・日常
ochtum
0
140
Featured
See All Featured
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
260
Leo the Paperboy
mayatellez
7
1.9k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
240
The Cost Of JavaScript in 2023
addyosmani
55
10k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.7k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
210
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
Chasing Engaging Ingredients in Design
codingconduct
0
230
GraphQLとの向き合い方2022年版
quramy
50
15k
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
170
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