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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
560
@Environment(\.keyPath)那么好我不允许你们不知道! / atEnvironment keyPath is so good and you should know it!
lovee
0
560
ゼロから始める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
780
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
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
120
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
250
JRuby: Past, Present, and Future
headius
0
210
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
270
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
18
19k
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
320
大喜利で理解するLLM as a Judge / Understanding LLM-as-a-Judge through Ogiri
rockname
0
170
更なる可用性を求めて、5年間運用したKotlinのアプリケーションをGoでリプレイスする話
ken_tunc
0
400
The Rails Doctrine Decade
koic
2
410
マイコン向けの軽量Ruby「PicoRuby」で各種デバイスを制御するネイティブアプリの実現手法
bash0c7
0
460
[ハンズオン]AIへの指示だけで「五目並べ」を作ってみよう
satoshi256kbyte
1
290
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
220
Featured
See All Featured
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.4k
Git: the NoSQL Database
bkeepers
PRO
432
67k
Product Roadmaps are Hard
iamctodd
55
13k
SEO for Brand Visibility & Recognition
aleyda
0
4.8k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
58k
Documentation Writing (for coders)
carmenintech
77
5.5k
Deep Space Network (abreviated)
tonyrice
0
330
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
250
Large-scale JavaScript Application Architecture
addyosmani
515
110k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
290
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