Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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
Tweet

More Decks by Elvis Shi

Other Decks in Programming

Transcript

  1. 4XJGU6*Ͱը໘ભҠͷঢ়ଶͱํ๏Λ

    ؅ཧ͢ΔϧʔλΛ࡞ͬͨ࿩
    f o r : 6 . & . * T X J G U ʙ ཪ J 0 4 % $ ʙ
    ͓ͨͩ͠קΊ͸͠ͳ͍

    View Slide

  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 {

    View Slide

  3. Ұൠతͳ4XJGU6*ͷը໘ભҠ
    ʢ.PEBMભҠʣ
    struct ParentView: View {


    @State var showsChild = false


    var body: some View {


    Text("Parent")


    .fullScreenCover(isPresented: $showsChild,


    content: { ChildView() })


    }


    } ભҠ੍ޚ༻

    .PEJ
    fi
    FS ભҠઌ
    ભҠϑϥά

    View Slide

  4. Ұൠతͳ4XJGU6*ͷը໘ભҠ
    ʢ1VTIભҠʣ
    struct ParentView: View {


    @State var showsChild = false


    var body: some View {


    NavigationLink(destination: { ChildView() },


    label: { Text("Show Child") })


    }


    } ભҠઌ
    ભҠϑϥάʜʁ

    View Slide

  5. Ұൠతͳ4XJGU6*ͷը໘ભҠ
    ʢ1VTIભҠʣ
    struct ParentView: View {


    @State var showsChild = false


    var body: some View {


    NavigationLink(isActive: $showsChild,


    destination: { ChildView() },


    label: { EmptyView() })


    }


    } ભҠઌ
    ભҠϑϥά
    ભҠ੍ޚ͕

    .PEJ
    fi
    FSͰ͸ͳ͍

    View Slide

  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

    View Slide

  7. 4XJGU6*ͷը໘ભҠʹඞཁͳ৘ใ
    w ભҠ੍ޚ༻ͷ.PEJ
    fi
    FS
    w ભҠϑϥά
    w ભҠઌ
    ϧʔλ಺
    ϧʔλ֎

    View Slide

  8. 3PVUFSΛ࡞Δ
    final class Router: ObservableObject {


    @Published var showsChild = false


    func navigationBinding() -> Binding {


    return .init(get: { [unowned self] in


    return self.showsChild


    },


    set: { [unowned self] in


    self.showsChild = $0


    })


    }


    @ViewBuilder func nextView() -> some View {


    ChildView()


    }


    }
    ભҠઌ
    ભҠϑϥά

    View Slide

  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() }


    ))


    }


    }

    View Slide

  10. ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠
    ͳ༁͋Δ͔ʂ

    View Slide

  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Λ׬શʹ

    ೺Ѳ͍ͯ͠ͳ͚Ε͹ͳΒͳ͍

    View Slide

  12. ݱঢ়ͷ໰୊఺
    w Ͱ͖Ε͹7JFXଆͰ͸3PVUFSΛҰ੾஌Γͨ͘ͳ͍
    w 3PVUFS͕ݺͼग़͠ݩͷ7JFXΛ೺ѲͰ͖ͳ͍
    w .PEJ
    fi
    FSͰ௕͍3PVUFSద༻ίʔυॻ͘ͷ͕ͩΔ͍

    View Slide

  13. ཧ૝
    protocol ParentRouterDelegate: ObservableObject {


    func parentDidSubmit()


    }


    struct ParentView: View {


    @ObservedObject var router: R


    var body: some View {


    Button {


    router.parentDidSubmit()


    } label: {


    Text("Submit")


    }


    }


    }
    ໘౗ͳ࡞ۀ͸্ҐϨΠϠʔͷ͸ͣͷ

    3PVUFSʹ೚͍ͤͨʂ

    View Slide

  14. Ξϓϩʔν
    w 3PVUFSͷந৅ԽΛ෼ׂ
    w 7JFX͔Β΍Γ͍ͨભҠˡ7JFXʹґଘ
    w ભҠΛ࣮ݱ͢ΔͨΊͷ࢓૊Έˡڞ௨ϩδοΫ

    View Slide

  15. 7JFX͔Β΍Γ͍ͨભҠ
    protocol ParentRouterDelegate: ObservableObject {


    func parentDidSubmit()


    }


    struct ParentView: View {


    @ObservedObject var router: R


    // ...


    }

    View Slide

  16. ભҠΛ࣮ݱ͢ΔͨΊͷ࢓૊Έ
    enum ViewID {


    //...


    }


    protocol RouterObject: ObservableObject {


    associatedtype NextView: View


    func pushFlag(for view: ViewID) -> Binding


    func modalFlag(for view: ViewID) -> Binding


    func nextView(after view: ViewID) -> NextView


    }


    struct RoutingModifier: ViewModifier {


    @ObservedObject var router: R


    var viewID: ViewID


    func body(content: Content) -> some View {


    content


    .background(//...


    .fullScreenCover(//...


    }


    }


    extension View {


    func injectRouter(_ router: R, as viewID: ViewID) -> some View {


    modifier(RoutingModifier(router: router, viewID: viewID))


    }


    }
    ͜ΕͰݺͼग़͠ݩ͕

    Θ͔Δ

    View Slide

  17. 3PVUFSΛ࣮૷

    View Slide

  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ʹࣗ෼ࣗ਎Λ஫ೖ͢Ε͹͍͍

    View Slide

  19. 3PVUFSͷભҠ%FMFHBUFద߹
    extension TestRouter: ParentRouterDelegate {




    func parentDidSubmit() {


    parentViewRoute = .child


    }




    }
    ભҠ͕ඞཁͳͱ͖ͷಈ࡞͚ͩΛ

    ઐ೦Ͱ͖Δ

    View Slide

  20. 3PVUFSͷભҠ࢓૊Έͷద߹
    extension TestRouter: TestRouterObject {


    func pushFlag(for view: ViewID) -> Binding {


    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 {


    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Λฦ͢

    View Slide

  21. ׬੒͸͕ͨ͠

    Φεεϝ͸͠ͳ͍

    😇

    View Slide

  22. %&.0

    View Slide

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

    View Slide

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

    View Slide

  25. ࢀߟ
    IUUQTHJUIVCDPNFMIPTIJOP4XJGU6*3PVUFS%FNP

    View Slide