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
A story about me trying to make a router that m...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Elvis Shi
September 27, 2021
Programming
500
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
590
@Environment(\.keyPath)那么好我不允许你们不知道! / atEnvironment keyPath is so good and you should know it!
lovee
0
580
ゼロから始めるPreferenceの実装 / Let's implement Preferences from scratch
lovee
0
170
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
410
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
800
How did I build an Open-Source SwiftUI Toast Library
lovee
1
190
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
460
Other Decks in Programming
See All in Programming
Event is what happens
moro
2
3k
S3 TablesのIcebergデータに、AIエージェントからアクセスしてみた / 20261016developersio2026joetsu-accessing-iceberg-in-s3tables-via-agent
kasacchiful
PRO
0
260
8年前のNode.jsを動かして分かった、npm installの裏側
keisukeikeda
0
120
Web Framework Benchmark Battle 2026, Any%
a_matsuda
3
3.1k
クラウドエージェントは、 何がそんなに便利なのか / what makes cloud agents so useful
rkaga
5
680
オーケストレーション記述言語 Katari
yukikurage
0
220
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
350
PumaからFalconに移行したRailsアプリケーションでの実践
falcon8823
0
340
FactoryBotをやめればRSpecは速くなるのか?
yuta_onishi_97
0
1.3k
LLM 101
dalinaum
0
450
プロジェクトコードをキレイキレイしよう!暗黙的な参照を読み解く静的解析
ostk0069
0
200
Engineering Biometric UX Across Android Versions | DroidKnights 2026
ryunen344
0
140
Featured
See All Featured
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.9k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
1.1k
ラッコキーワード サービス紹介資料
rakko
5
5.6M
What the history of the web can teach us about the future of AI
inesmontani
PRO
2
780
Prompt Engineering for Job Search
mfonobong
0
570
Music & Morning Musume
bryan
48
7.5k
What does AI have to do with Human Rights?
axbom
PRO
1
2.5k
Designing Powerful Visuals for Engaging Learning
tmiket
1
630
Rails Girls Zürich Keynote
gr2m
96
14k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
970
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.2k
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