Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Coordinatables on v2.0.0 #28

Open
jblanco-applaudostudios opened this issue Sep 29, 2021 · 3 comments
Open

Custom Coordinatables on v2.0.0 #28

jblanco-applaudostudios opened this issue Sep 29, 2021 · 3 comments

Comments

@jblanco-applaudostudios

Just wondering if you had an example of the hamburger menu using the new version, I can't seem to find it in the source.
I'm having trouble understanding how to write a custom router using the new system.

@rundfunk47
Copy link
Owner

rundfunk47 commented Sep 29, 2021

Hi! Here is a quick and dirty example:

class BurgerOpener: ObservableObject {
    @Published var value: Bool
    
    init(value: Bool) {
        self.value = value
    }
}

class SplitViewCoordinator<Master: Coordinatable, Detail: Coordinatable>: Coordinatable {
    typealias Router = SplitViewRouter<Master, Detail>

    public let master: Master
    public let detail: Detail
    var opened = BurgerOpener(value: false)

    public func dismissChild(coordinator: AnyCoordinatable, action: (() -> Void)?) {
        fatalError("Not implemented")
    }
    
    public weak var parent: AnyCoordinatable?

    public func view() -> AnyView {
        AnyView(
            SplitViewCoordinatorView(
                coordinator: self
            )
        )
    }
    
    public init(detail: Detail, master: Master) {
        self.detail = detail
        self.master = master
        self.detail.parent = self
        self.master.parent = self
    }
    
    func toggle() {
        opened.value.toggle()
    }
}

struct SplitViewCoordinatorView<Master: Coordinatable, Detail: Coordinatable>: View {
    @ObservedObject var opened: BurgerOpener
    let coordinator: SplitViewCoordinator<Master, Detail>
    let router: SplitViewRouter<Master, Detail>
    
    var body: some View {
        Group {
            if opened.value == true {
                HStack {
                    coordinator.master.view()
                    coordinator.detail.view()
                }
            } else {
                coordinator.master.view()
            }
        }
        .environmentObject(router)
    }
    
    init(coordinator: SplitViewCoordinator<Master, Detail>) {
        self.opened = coordinator.opened
        self.coordinator = coordinator
        self.router = SplitViewRouter(coordinator: coordinator)
        RouterStore.shared.store(router: router)
    }
}

class SplitViewRouter<Master: Coordinatable, Detail: Coordinatable>: Routable {
    fileprivate weak var coordinator: SplitViewCoordinator<Master, Detail>!
    
    init(coordinator: SplitViewCoordinator<Master, Detail>) {
        self.coordinator = coordinator
    }
    
    func toggle() {
        coordinator.toggle()
    }
    
    var detail: Detail {
        coordinator.detail
    }
    
    var master: Master {
        coordinator.master
    }
}

to use it:

struct StinsenApp: App {
    var body: some Scene {
        WindowGroup {
            SplitViewCoordinator(
                detail: MainCoordinator(),
                master: MainCoordinator()
            ).view()
        }
    }
}

To fetch the router:

    @EnvironmentObject private var splitViewRouter: SplitViewCoordinator<MainCoordinator, MainCoordinator>.Router

Hopefully this is easier to follow than in version 1.

To you need to switch detail/master, thanks to the new system in version 2, if you replace the first MainCoordinator in with for instance

class DetailCoordinator: NavigationCoordinatable {
   var stack = NavigationStack(initial: \.a)

   @Root var a = makeA
   @Root var b = makeB
[...]

You can switch using:

splitViewRouter.detail.setRoot(\.b)

I think SplitView is a common enough use case, so maybe this should be provided as an example or even built into Stinsen itself - but I still haven't decided on the best course of action here... Suggestions? :)

@jblanco-applaudostudios
Copy link
Author

Thanks for your help! I was able to use the sample code you provided to create a custom SplitView on my App. I only found one issue though, I have this SplitViewCoordinator inside a custom SidebarCoordinator that I made, which works exactly the same as the TabViewCoordinatable (I dug into the source and replicated the code, changing the view). When changing navigating through my menu, when I change the option and return to the one I was in before, the SplitViewCoordinator's view reinitializes the viewmodel. In this case, this is not desirable. Is there any way to persist the views so they don't change? 🤔

By the way... I noticed that when I set the master and detail for the SplitViewCoordinator, if any of the views in the coordinators has a large navigation bar title, it doesn't display correctly (The status bar loses the color only on the part in which the large navigation title is).

Sure, I have suggestions 😄 Let me create a separate issue for that!

@rundfunk47
Copy link
Owner

The reinitialization of the ViewModel sounds more like a SwiftUI issue than a Stinsen one (although it might be, but I checked the code quickly and it seems like we should persist views rather than recreate...) since SwiftUI will recreate views very often. Perhaps you can persist the viewmodel in the parent view, or maybe you can just try toggling the opacity/changing the offset instead of removing the view completely?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants