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
redacted を TCA でスマートに扱う
Search
Aikawa
October 24, 2020
Programming
1k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
redacted を TCA でスマートに扱う
Aikawa
October 24, 2020
More Decks by Aikawa
See All by Aikawa
DocC Tutorial と TCA におけるテスト機能の紹介
kalupas226
1
1.8k
Swift愛好会WWDC要約会 Build programmatic UI with Xcode Previews
kalupas226
2
910
enum で KeyPaths のような機能を実現する CasePaths
kalupas226
4
1.1k
SwiftUI Navigation のすべて
kalupas226
11
9.8k
Refreshable API を TCA で使う
kalupas226
0
310
Combineを使ったコードのテストをSchedulerで操る方法とその仕組み
kalupas226
2
2k
Composable FormsでTCAのボイラープレートとおさらばする
kalupas226
1
1.4k
Swiftの関数と代数学
kalupas226
0
920
Swiftのstruct・enumと代数学_part1
kalupas226
2
1.7k
Other Decks in Programming
See All in Programming
Enforcing Your Architecture with Unit Tests in the AI Era (Swift Connection X)
steliosf
PRO
1
140
AWS CDKを理解するために公式の定義を深掘って解説してみる / Understanding AWS CDK by Digging into Its Official Definition
akihisaikeda
2
260
Getting Started with Vibe Coding
miccheng
0
180
Kawaiicon: Kubernetes, it's no longer cool, but let's secure it anyway!
barnbarn
0
110
AI-Powered Domain-Driven Design
ziobrando
0
390
SFCで実現する病院・医療システムの動的組み立て式UI
koukimiura
1
170
AI時代のシニアエンジニアとは
yosuke_furukawa
PRO
14
11k
AIが生成したコードは読むべきか?抽象化設計とコードの品質保証 / abstraction-design-and-quality
minodriven
16
8.6k
Crash Auto Pipelines
veronikapj
1
350
AIが実装を担う時代の「技術設計」と「チームマネジメント」
kamina_zzz
1
170
PumaからFalconに移行したRailsアプリケーションでの実践
falcon8823
0
350
そのデザイントークン、Flutterでも同じ見た目になりますか?
nakamizu
0
300
Featured
See All Featured
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
470
Designing Experiences People Love
moore
143
24k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.6k
YesSQL, Process and Tooling at Scale
rocio
174
15k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
440
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
760
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
320
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.4k
Producing Creativity
orderedlist
PRO
348
41k
Building Better People: How to give real-time feedback that sticks.
wjessup
371
20k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
630
Transcript
redacted を TCA でスマートに扱う iOS アプリ開発のためのFunctional Architecture 情報共有会
⾃⼰紹介 アイカワ(@kalupas0930 ) 新卒 iOS エンジニア 函館出⾝ 最近は Flutter, 機械学習の勉強をしてます
SwiftUI と Combine もまだまだ勉強中です 2
redacted とは? SkeltetonView のようなもの SwiftUI で iOS14 から使⽤できる ViewModifier とても便利
3
4
SwiftUI でどう使う? Text("This is redacted") .redacted(reason: .placeholder) redacted Modifier を付けるだけ
5
ViewModifier ならではの使い⽅ VStack { Image("kuma") .resizable() .frame(width: 100, height: 100)
.clipShape(Circle()) Text("This is redacted") Text("kuma") } 6
ViewModifier ならではの使い⽅ ViewModifier なので簡単に Skeleton を 表現できる VStack { Image("kuma")
.resizable() .frame(width: 100, height: 100) .clipShape(Circle()) Text("This is redacted") Text("kuma") } .redacted(reason: .placeholder) 7
もう少しだけ redacted について深掘り redacted は以下のように定義されている func redacted(reason: RedactionReasons) -> some
View RedactionReasons は? 現時点では先ほど紹介した placeholder しか持っていない OptionSet に適合しているため、将来的には他の reason も 使えるようになるかもしれない ちなみに現時点でもオリジナルの ReactionReasons を作って、 reason を使い分けることはできる 8
状態管理含めた時の redacted を⾒ていきます まずは @ObservableObejct を利⽤した TCA を利⽤しない SwiftUI での使⽤⽅法を⾒ていく
単純なリスト表⽰をするだけのアプリを作る 9
10
扱う状態 struct Item: Equatable, Identifiable { let id: UUID let
title: String let description: String } 基本的な title と description を持っているだけ 11
プレースホルダー⽤の変数 let placeholderListItem = (0...10).map { _ in Item( id:
.init(), title: String(repeating: " ", count: .random(in: 50...100)), description: String(repeating: " ", count: .random(in: 10...30)) ) } title と description は適当にスペースで埋めてそれっぽくしている 12
ロード完了後⽤の変数 let liveListItem = [ Item(id: .init(), title: " これは
redacted", description: String(repeating: " おはよう", count: 10)), Item(id: .init(), title: "This is redacted", description: String(repeating: "Good morning", count: 10)), Item(id: .init(), title: " よろしくお願いします", description: String(repeating: "yes,yes", count: 10)) ] 中⾝は適当です 13
状態管理⽤の ObservableObject class ListItemViewModel: ObservableObject { @Published var listItem: [Item]
= [] @Published var isLoading = false init() { isLoading = true // 4s 経ったら⾃動的に動作するようにする DispatchQueue.main.asyncAfter(deadline: .now() + 4){ self.isLoading = false self.listItem = liveListItem } } } 14
View を少しずつ⾒ていきます @ObservedObject private var viewModel = ListItemViewModel() var body:
some View { List { if viewModel.isLoading { ActivityIndicator().frame(maxWidth: .infinity).padding() } ForEach( ... // 繰り返す Item Button(action: { ... // ボタンを押した時のアクション }) { ... // ボタンの View } } } 15
ForEach の中⾝ / ボタンのアクション @ObservedObject private var viewModel = ListItemViewModel()
... // 省略 ForEach( viewModel.isLoading ? placeholderListItem : viewModel.listItem) { item in Button(action: { guard !self.viewModel.isLoading else { return } print("Button was tapped") }) { ... // ボタンの View } } 16
ForEach の中⾝ / ボタンの View @ObservedObject private var viewModel =
ListItemViewModel() ... // 省略 ForEach( ... ) { item in Button(action: { ... }) { HStack(alignment: .top) { Image("kuma") .resizable() .frame(width: 80, height: 80) VStack(alignment: .leading, spacing: 10) { Text(item.title).font(.title2) Text(item.description).font(.body) } } } } 17
redacted を追加 @ObservedObject private var viewModel = ListItemViewModel() var body:
some View { List { if viewModel.isLoading { ActivityIndicator().frame(maxWidth: .infinity).padding() } ForEach( ... // 繰り返す Item Button(action: { ... // ボタンを押した時のアクション }) { ... // ボタンの View } .redacted(reason: viewModel.isLoading ? .placeholder: []) } } 18
disabled も追加 @ObservedObject private var viewModel = ListItemViewModel() var body:
some View { List { if viewModel.isLoading { ActivityIndicator().frame(maxWidth: .infinity).padding() } ForEach( ... // 繰り返す Item Button(action: { ... // ボタンを押した時のアクション }) { ... // ボタンの View } .redacted(reason: viewModel.isLoading ? .placeholder: []) .disabled(viewModel.isLoading) // これを追加 } 19
やりたいことは実現できた しかし、この⽅法には問題点がある View のあちこちで viewModel.isLoading を使っている 状態が増えてきた時に開発者が気にしなければならないことが 多くなってしまう disabled によってロード中はタップできないようにできたが、
disabled の利⽤シーンとしては微妙 もし onAppear などがあった際、それを防ぐことはできない ⾊が少し明るくなってしまうので、本来意図している View の⾊ とは異なるものになるかもしれない 20
The Composable Architecture なら? 基本的な TCA の流れ View から Action
を送る Action によって Reducer で Store の State が変更される イメージは isLoading によって Store を使い分ける isLoading が true ( ロード中) : プレースホルダー⽤の Store false ( ロード完了): 本物の Store 21
実際に TCA を使った例を紹介します まずは State struct ListItemState: Equatable { var
listItem: [Item] = [] var isLoading = false } 先ほどの @ObservableObject を利⽤した class と⼤きな差はない State は Action を通じてのみ変更されるため、struct 内に 状態を変化させるための関数はない 22
Action enum ListItemAction { case listItemResponse([Item]?) case onAppear } View
の onAppear 時に呼ばれる Action その Action によって発⽕する listItemResponse([Item]?) 23
Reducer let listItemReducer = Reducer<ListItemState, ListItemAction, Void> { state, action,
environment in switch action { case let .listItemResponse(listItem): state.isLoading = false state.listItem = listItem ?? [] return .none case .onAppear: state.isLoading = true return Effect(value: .listItemResponse(liveListItem)) .delay(for: 4, scheduler: DispatchQueue.main) .eraseToEffect() } } onAppear してから、わざと 4s 遅らせるようにして API 通信してる⾵ にしているだけ 24
View の全体像 let store: Store<ListItemState, ListItemAction> var body: some View
{ WithViewStore(store) { viewStore in List { if viewStore.isLoading { ActivityIndicator().padding().frame(maxWidth: .infinity) } ListItemView( // ( プレースホルダー store) or ( 本物 store) を渡す ) .redacted(reason: viewStore.isLoading ? .placeholder : []) } .onAppear { viewStore.send(.onAppear) } } } 25
ListItemView への Store の渡し⽅ ListItemView( store: viewStore.isLoading ? Store( initialState:
.init(listItem: placeholderListItem), reducer: .empty, environment: () ) : self.store ) .redacted(reason: viewStore.isLoading ? .placeholder : []) viewStore.isLoading によって以下を渡す true : placeholder ⽤ Store false : 本物の Store 26
⼀応 ListItemView の中⾝ let store: Store<ListItemState, ListItemAction> var body: some
View { WithViewStore(store) { viewStore in ForEach(viewStore.listItem) { item in // 本当は ForEachStore などを使うと良い Button(action: { // ここで viewStore.send() としても、placeholder store であれば // state に影響はないので、send し放題 }) { HStack(alignment: .top) { Image("kuma").resizable().frame(width: 80, height: 80) VStack(alignment: .leading, spacing: 10) { Text(item.title).font(.title2) Text(item.description).font(.body) } } } .buttonStyle(PlainButtonStyle()) } } 27
TCA と redacted を組み合わせれば 開発者は最初に isLoading の状態によって、Store を使い分ける という判断だけで良くなる disabled
を使⽤せずとも、「ローディング中のセルをタップ しても何も起きないようにする」という動作を実現できた 今回は扱う State を説明のために絞ったが、State が多くなれば なるほど TCA の恩恵を受けることができる 28
おわりに 今回発表した内容は Point-Free さんの Episode115~ の redacted についての記事を参考にしました https://www.pointfree.co/ 記事ではもっと深掘りされた内容が書かれています
扱う状態が増えた時の redacted の扱い⽅ 画⾯も⼀つではなく複数 コンテンツの多くは有料かつ動画は英語ですが、スクリプトが あるので英語が苦⼿でも翻訳頼りで理解できます 29