Skip to content

Instantly share code, notes, and snippets.

@maximkrouk
Last active June 29, 2020 01:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maximkrouk/6c5c759bea67080634b43737c810ec14 to your computer and use it in GitHub Desktop.
Save maximkrouk/6c5c759bea67080634b43737c810ec14 to your computer and use it in GitHub Desktop.
import SwiftUI
// MARK: - Extensions
extension EnvironmentObject {
fileprivate var hasValue: Bool {
Mirror(reflecting: self).children.contains { child in
guard child.label == "_store" else { return false }
return child.value is ViewRouter
}
}
}
// MARK: - Router
fileprivate class ViewRouter: ObservableObject, RoutingProxy {
@Published
var viewStack: [AnyView] = []
init() {}
func route<T: View>(to view: T) {
self.viewStack.append(view.eraseToAnyView())
self.objectWillChange.send()
}
func dismiss() {
_ = self.viewStack.popLast()
self.objectWillChange.send()
}
}
protocol RoutingProxy: AnyObject {
func route<T: View>(to view: T)
func dismiss()
}
// MARK: - View
struct RoutingView: View {
@ObservedObject
private var router: ViewRouter
init<Content: View>(@ViewBuilder content: (RoutingProxy) -> Content) {
self.router = ViewRouter()
router.route(to: content(router))
}
init<Content: View>(@ViewBuilder content: () -> Content) {
self.router = ViewRouter()
router.route(to: content())
}
var body: some View {
router.viewStack.last
.or(EmptyView().eraseToAnyView())
.environmentObject(router)
}
}
// MARK: - Links
struct RoutingLink<Label: View, Destination: View>: View {
@EnvironmentObject
fileprivate var router: ViewRouter
var destination: Destination
var label: Label
init(
destination: Destination,
@ViewBuilder label: () -> Label
) {
self.destination = destination
self.label = label()
}
var body: Button<Label> {
Button(action: {
if self._router.hasValue {
self.router.route(to: self.destination)
}
}) { label }
}
}
struct DismissLink<Label: View>: View {
@EnvironmentObject
fileprivate var router: ViewRouter
var label: Label
init(@ViewBuilder label: () -> Label) {
self.label = label()
}
var body: Button<Label> {
Button(action: {
if self._router.hasValue {
self.router.dismiss()
}
}) { label }
}
}
struct DismissView<Content: View>: View {
@EnvironmentObject
fileprivate var router: ViewRouter
@Binding var isActive: Bool
var content: Content
var body: some View {
if !isActive { router.dismiss() }
return content
}
}
@maximkrouk
Copy link
Author

maximkrouk commented Jun 14, 2020

Usage

import SwiftUI

struct ContentView: View {
    var body: some View {
        RootView()
    }
}

struct RootView: View {
    var body: some View {
        VStack {
            RoutingView {
                RoutingLink(destination: FirstView()) { Text("FirstView") }
                    .padding()
            }
            Divider()
            RoutingView {
                RoutingLink(destination: SecondView()) { Text("SecondView") }
                    .padding()
            }
        }.padding()
    }
}

struct FirstView: View {
    var body: some View {
        VStack {
            Text("FirstView")
            DismissLink { Text("Back") }.padding()
            RoutingLink(destination: ThirdView()) { Text("ThirdView") }
                .padding()
        }
    }
}

struct SecondView: View {
    var body: some View {
        VStack {
            Text("SecondView")
            DismissLink { Text("Back") }.padding()
            RoutingLink(destination: ThirdView()) { Text("ThirdView") }
                .padding()
        }
    }
}

struct ThirdView: View {
    var body: some View {
        VStack {
            Text("ThirdView")
            DismissLink { Text("Back") }.padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Back to index

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment