Save 37% off PRO during our Black Friday Sale! »

A story about me trying to make a router that manages when, how and which view to transit in a SwiftUI app

Af64bc38c0ffcfcabdf430759ee491ce?s=47 Elvis Shi
September 27, 2021

A story about me trying to make a router that manages when, how and which view to transit in a SwiftUI app

Af64bc38c0ffcfcabdf430759ee491ce?s=128

Elvis Shi

September 27, 2021
Tweet

Transcript

  1. 4XJGU6*Ͱը໘ભҠͷঢ়ଶͱํ๏Λ 
 ؅ཧ͢ΔϧʔλΛ࡞ͬͨ࿩ f o r  : 6 .

    & . *  T X J G U      ʙ ཪ J 0 4 % $ ʙ ͓ͨͩ͠קΊ͸͠ͳ͍
  2. } 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 {
  3. Ұൠతͳ4XJGU6*ͷը໘ભҠ ʢ.PEBMભҠʣ struct ParentView: View { @State var showsChild =

    false var body: some View { Text("Parent") .fullScreenCover(isPresented: $showsChild, content: { ChildView() }) } } ભҠ੍ޚ༻ 
 .PEJ fi FS ભҠઌ ભҠϑϥά
  4. Ұൠతͳ4XJGU6*ͷը໘ભҠ ʢ1VTIભҠʣ struct ParentView: View { @State var showsChild =

    false var body: some View { NavigationLink(destination: { ChildView() }, label: { Text("Show Child") }) } } ભҠઌ ભҠϑϥάʜʁ
  5. Ұൠతͳ4XJGU6*ͷը໘ભҠ ʢ1VTIભҠʣ struct ParentView: View { @State var showsChild =

    false var body: some View { NavigationLink(isActive: $showsChild, destination: { ChildView() }, label: { EmptyView() }) } } ભҠઌ ભҠϑϥά ભҠ੍ޚ͕ 
 .PEJ fi FSͰ͸ͳ͍
  6. Ұൠతͳ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
  7. 4XJGU6*ͷը໘ભҠʹඞཁͳ৘ใ w ભҠ੍ޚ༻ͷ.PEJ fi FS w ભҠϑϥά w ભҠઌ ϧʔλ಺

    ϧʔλ֎
  8. 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() } } ભҠઌ ભҠϑϥά
  9. 3PVUFSΛ࢖͏ struct ParentView: View { @StateObject var router = Router()

    var body: some View { Text("Parent") .background(NavigationLink( isActive: router.navigationBinding(), destination: { router.nextView() }, label: { EmptyView() } )) } }
  10. ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ ͳ༁͋Δ͔ʂ

  11. 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Λ׬શʹ 
 ೺Ѳ͍ͯ͠ͳ͚Ε͹ͳΒͳ͍
  12. ݱঢ়ͷ໰୊఺ w Ͱ͖Ε͹7JFXଆͰ͸3PVUFSΛҰ੾஌Γͨ͘ͳ͍ w 3PVUFS͕ݺͼग़͠ݩͷ7JFXΛ೺ѲͰ͖ͳ͍ w .PEJ fi FSͰ௕͍3PVUFSద༻ίʔυॻ͘ͷ͕ͩΔ͍

  13. ཧ૝ 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ʹ೚͍ͤͨʂ
  14. Ξϓϩʔν w 3PVUFSͷந৅ԽΛ෼ׂ w 7JFX͔Β΍Γ͍ͨભҠˡ7JFXʹґଘ w ભҠΛ࣮ݱ͢ΔͨΊͷ࢓૊Έˡڞ௨ϩδοΫ

  15. 7JFX͔Β΍Γ͍ͨભҠ protocol ParentRouterDelegate: ObservableObject { func parentDidSubmit() } struct ParentView<R:

    ParentRouterDelegate>: View { @ObservedObject var router: R // ... }
  16. ભҠΛ࣮ݱ͢ΔͨΊͷ࢓૊Έ 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)) } } ͜ΕͰݺͼग़͠ݩ͕ 
 Θ͔Δ
  17. 3PVUFSΛ࣮૷

  18. 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ʹࣗ෼ࣗ਎Λ஫ೖ͢Ε͹͍͍
  19. 3PVUFSͷભҠ%FMFHBUFద߹ extension TestRouter: ParentRouterDelegate { func parentDidSubmit() { parentViewRoute =

    .child } } ભҠ͕ඞཁͳͱ͖ͷಈ࡞͚ͩΛ 
 ઐ೦Ͱ͖Δ
  20. 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Λฦ͢
  21. ׬੒͸͕ͨ͠ 
 Φεεϝ͸͠ͳ͍ 
 😇

  22. %&.0

  23. ໰୊఺ w ֊૚Λލ͙1PQΞχϝʔγϣϯ͕͓͔͍͕࣌͋͠Δ w J04Ͱͦ΋ͦ΋֊૚Λލ͙1VTIભҠ͕Ͱ͖ͳ͍ w ଞʹ΋4XJGU6*ͷόά͕͋Δ͔΋͠Εͳ͍

  24. ݱ࣮తʹ͸6*,JUܦ༝Ͱ 3PVUFSΛ࡞ͬͨํ͕͍͍͔΋

  25. ࢀߟ IUUQTHJUIVCDPNFMIPTIJOP4XJGU6*3PVUFS%FNP