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
Elvis Shi
September 27, 2021
Programming
490
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
540
@Environment(\.keyPath)那么好我不允许你们不知道! / atEnvironment keyPath is so good and you should know it!
lovee
0
540
ゼロから始めるPreferenceの実装 / Let's implement Preferences from scratch
lovee
0
160
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
400
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
770
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
450
Other Decks in Programming
See All in Programming
The Good Stuff, Not the Slop: Engineering High-Quality Android Apps with Modern AI Tooling
danybony
1
260
[GoCon2026] When Goroutines Are Not Enough: Runtime Locality in High-Throughput Go
takehaya
6
2.3k
標準パッケージに uuid が追加された 背景から見る Go らしい意思決定 / go_127_uuid_decision
convto
5
7.5k
JRuby: Past, Present, and Future
headius
0
140
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
7
3k
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
5
3.4k
AHC070解法紹介
eijirou
0
120
Building an Out-of-Order CPU
latte72
1
790
Intent as Code
shoppingjaws
6
1k
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
180
AI × TiDD / 2026.09.05 Redmine 大阪
tokudiro
1
180
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
220
Featured
See All Featured
Why Our Code Smells
bkeepers
PRO
340
58k
Balancing Empowerment & Direction
lara
6
1.3k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
1
530
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
300
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
The SEO Collaboration Effect
kristinabergwall1
1
570
Building Adaptive Systems
keathley
44
3.2k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Utilizing Notion as your number one productivity tool
mfonobong
4
590
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
580
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
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