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
A story about me trying to make a router that m...
Search
Elvis Shi
September 27, 2021
Programming
480
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
A story about me trying to make a router that manages when, how and which view to transit in a SwiftUI app
Elvis Shi
September 27, 2021
More Decks by Elvis Shi
See All by Elvis Shi
為什麼你並不需要ViewModel / No, you don't need a ViewModel
lovee
1
440
@Environment(\.keyPath)那么好我不允许你们不知道! / atEnvironment keyPath is so good and you should know it!
lovee
0
510
ゼロから始めるPreferenceの実装 / Let's implement Preferences from scratch
lovee
0
150
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
390
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
750
How did I build an Open-Source SwiftUI Toast Library
lovee
1
170
SwiftUIで使いやすいToastの作り方 / How to build a Toast system which is easy to use in SwiftUI
lovee
3
1.3k
SwiftUIで二重スクロール作ってみた / When I tried to make a dual-scroll-ish view in SwiftUI
lovee
1
390
Observation のあれこれ / A brief introduction about Observation
lovee
3
440
Other Decks in Programming
See All in Programming
使いながら育てる Claude Code — 開発フローの1コマンド化 × 繰り返し指摘の自動仕組み化
shiki_kakaku
0
1.2k
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
430
コーディングルールの鮮度を保ちたい for SRE NEXT 2026 / keep-fresh-go-internal-conventions-sre-next-2026
handlename
0
150
地域 SRE コミュニティ最前線 - ホンマでっかSRE勉強会
tk3fftk
0
280
ここ半年くらいでAIに作らせたR用ツール
eitsupi
0
330
AI時代の仕事技芸論〜ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ(スクフェス仙台 2026バージョン)
kuranuki
0
750
自動化したのに回らないテスト運用の壁ーAI時代の品質責任と生産性
mfunaki
0
110
jsmini JavaScript Engine を作ってみた話
yosuke_furukawa
PRO
0
250
Haskell/Servantを通してWebミドルウェアを捉え直す
pizzacat83
1
620
AIが無かった頃の素敵な出会いの話
codmoninc
1
270
5分で問診!Composer セキュリティ健康診断
codmoninc
0
680
PHP Application における Kubernetes 内 gRPC 通信
ganchiku
0
550
Featured
See All Featured
Believing is Seeing
oripsolob
1
170
VelocityConf: Rendering Performance Case Studies
addyosmani
333
25k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
240
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
810
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
190
WCS-LA-2024
lcolladotor
0
760
Documentation Writing (for coders)
carmenintech
77
5.4k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
360
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
390
How to Talk to Developers About Accessibility
jct
2
440
Transcript
4XJGU6*Ͱը໘ભҠͷঢ়ଶͱํ๏Λ ཧ͢ΔϧʔλΛ࡞ͬͨ f o r : 6 .
& . * T X J G U ʙ ཪ J 0 4 % $ ʙ ͓ͨͩ͠קΊ͠ͳ͍
} var employedBy = "YUMEMI Inc." var job = "iOS
Tech Lead" var favoriteLanguage = "Swift" var twitter = "@lovee" var qiita = "lovee" var github = "el-hoshino" var additionalInfo = """ ϒϩάॻ͘·Ͱ͕ iOSDC ͩΑʁ """ final class Me: Developable, Talkable {
Ұൠతͳ4XJGU6*ͷը໘ભҠ ʢ.PEBMભҠʣ struct ParentView: View { @State var showsChild =
false var body: some View { Text("Parent") .fullScreenCover(isPresented: $showsChild, content: { ChildView() }) } } ભҠ੍ޚ༻ .PEJ fi FS ભҠઌ ભҠϑϥά
Ұൠతͳ4XJGU6*ͷը໘ભҠ ʢ1VTIભҠʣ struct ParentView: View { @State var showsChild =
false var body: some View { NavigationLink(destination: { ChildView() }, label: { Text("Show Child") }) } } ભҠઌ ભҠϑϥάʜʁ
Ұൠతͳ4XJGU6*ͷը໘ભҠ ʢ1VTIભҠʣ struct ParentView: View { @State var showsChild =
false var body: some View { NavigationLink(isActive: $showsChild, destination: { ChildView() }, label: { EmptyView() }) } } ભҠઌ ભҠϑϥά ભҠ੍ޚ͕ .PEJ fi FSͰͳ͍
Ұൠతͳ4XJGU6*ͷը໘ભҠ ʢ1VTIભҠʣ struct ParentView: View { @State var showsChild =
false var body: some View { Text("Parent") .background(NavigationLink( isActive: $showsChild, destination: { ChildView() }, label: { EmptyView() } )) } } ભҠઌ ભҠϑϥά ભҠ੍ޚ༻ .PEJ fi FS
4XJGU6*ͷը໘ભҠʹඞཁͳใ w ભҠ੍ޚ༻ͷ.PEJ fi FS w ભҠϑϥά w ભҠઌ ϧʔλ
ϧʔλ֎
3PVUFSΛ࡞Δ final class Router: ObservableObject { @Published var showsChild =
false func navigationBinding() -> Binding<Bool> { return .init(get: { [unowned self] in return self.showsChild }, set: { [unowned self] in self.showsChild = $0 }) } @ViewBuilder func nextView() -> some View { ChildView() } } ભҠઌ ભҠϑϥά
3PVUFSΛ͏ struct ParentView: View { @StateObject var router = Router()
var body: some View { Text("Parent") .background(NavigationLink( isActive: router.navigationBinding(), destination: { router.nextView() }, label: { EmptyView() } )) } }
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ ͳ༁͋Δ͔ʂ
3PVUFSΛ͏ struct ParentView: View { @StateObject var router = Router()
var body: some View { Text("Parent") .background(NavigationLink( isActive: router.navigationBinding(), destination: { router.nextView() }, label: { EmptyView() } )) } } 7JFX͕3PVUFSΛશʹ Ѳ͍ͯ͠ͳ͚ΕͳΒͳ͍
ݱঢ়ͷ w Ͱ͖Ε7JFXଆͰ3PVUFSΛҰΓͨ͘ͳ͍ w 3PVUFS͕ݺͼग़͠ݩͷ7JFXΛѲͰ͖ͳ͍ w .PEJ fi FSͰ͍3PVUFSద༻ίʔυॻ͘ͷ͕ͩΔ͍
ཧ protocol ParentRouterDelegate: ObservableObject { func parentDidSubmit() } struct ParentView<R:
ParentRouterDelegate>: View { @ObservedObject var router: R var body: some View { Button { router.parentDidSubmit() } label: { Text("Submit") } } } ໘ͳ࡞ۀ্ҐϨΠϠʔͷͣͷ 3PVUFSʹ͍ͤͨʂ
Ξϓϩʔν w 3PVUFSͷநԽΛׂ w 7JFX͔ΒΓ͍ͨભҠˡ7JFXʹґଘ w ભҠΛ࣮ݱ͢ΔͨΊͷΈˡڞ௨ϩδοΫ
7JFX͔ΒΓ͍ͨભҠ protocol ParentRouterDelegate: ObservableObject { func parentDidSubmit() } struct ParentView<R:
ParentRouterDelegate>: View { @ObservedObject var router: R // ... }
ભҠΛ࣮ݱ͢ΔͨΊͷΈ enum ViewID { //... } protocol RouterObject: ObservableObject {
associatedtype NextView: View func pushFlag(for view: ViewID) -> Binding<Bool> func modalFlag(for view: ViewID) -> Binding<Bool> func nextView(after view: ViewID) -> NextView } struct RoutingModifier<R: RouterObject>: ViewModifier { @ObservedObject var router: R var viewID: ViewID func body(content: Content) -> some View { content .background(//... .fullScreenCover(//... } } extension View { func injectRouter<R: RouterObject>(_ router: R, as viewID: ViewID) -> some View { modifier(RoutingModifier(router: router, viewID: viewID)) } } ͜ΕͰݺͼग़͠ݩ͕ Θ͔Δ
3PVUFSΛ࣮
3PVUFSͷॳظ࣮ final class Router: ObservableObject { @Published var parentViewRoute: ViewID?
// ... func makeParentView() -> some View { ParentView(router: self) .injectRouter(self, as: .parent) } func makeChildView() -> some View { ChildView() } } 3PVUFS͕ඞཁʹԠͯ͡ 7JFXʹࣗࣗΛೖ͢Ε͍͍
3PVUFSͷભҠ%FMFHBUFద߹ extension TestRouter: ParentRouterDelegate { func parentDidSubmit() { parentViewRoute =
.child } } ભҠ͕ඞཁͳͱ͖ͷಈ࡞͚ͩΛ ઐ೦Ͱ͖Δ
3PVUFSͷભҠΈͷద߹ extension TestRouter: TestRouterObject { func pushFlag(for view: ViewID) ->
Binding<Bool> { switch view { case .parent: return .init(get: { [unowned self] in self.parentViewRoute != nil }, set: { [unowned self] in assert($0 == false); self.parentViewRoute = nil }) case .child: return .constant(false) } } func modalFlag(for view: ViewID) -> Binding<Bool> { return .constant(false) } @ViewBuilder func nextView(after view: ViewID) -> some View { switch view { case .parent: if let route = parentViewRoute, route == .child { makeChildView() } case .child: EmptyView() } } } 4XJGU6*ͷ্༷ɺ جຊ͜͜ݺΕΔͷ ΔભҠ࣌ͷΈͳͷͰ ͳ͍ͣͷભҠ DPOTUBOU GBMTF Ͱ ฦ͍͍ͤ ঢ়گʹԠͯ͡ 7JFXΛฦ͢
͕ͨ͠ Φεεϝ͠ͳ͍ 😇
%&.0
w ֊Λލ͙1PQΞχϝʔγϣϯ͕͓͔͍͕࣌͋͠Δ w J04Ͱͦͦ֊Λލ͙1VTIભҠ͕Ͱ͖ͳ͍ w ଞʹ4XJGU6*ͷόά͕͋Δ͔͠Εͳ͍
ݱ࣮తʹ6*,JUܦ༝Ͱ 3PVUFSΛ࡞ͬͨํ͕͍͍͔
ࢀߟ IUUQTHJUIVCDPNFMIPTIJOP4XJGU6*3PVUFS%FNP