Simple coordinator with router
- Change tab TabView
- Navigation by NavigationPath
- Present sheet and with dynamically height sheet
- Present full screen cover
- Alert with error
- Route from widget through coordinator
|
protocol NavigationRouter: Hashable, Identifiable { |
|
associatedtype V: View |
|
|
|
var style: TransitionStyle? { get } |
|
|
|
@ViewBuilder |
|
func view() -> V |
|
} |
|
class Coordinator<Router: NavigationRouter>: ObservableObject { |
|
|
|
@Published var path = NavigationPath() |
|
|
|
@Published var sheet: Router? |
|
@Published var cover: Router? |
|
|
|
@Published var hasError: Bool = false |
|
|
|
var error: LocalizedError? |
|
|
|
func push(_ page: Router) { |
|
path.append(page) |
|
} |
|
|
|
func pop() { |
|
path.removeLast() |
|
} |
|
|
|
func popToRoot() { |
|
path.removeLast(path.count) |
|
} |
|
|
|
func present(sheet: Router) { |
|
self.sheet = sheet |
|
} |
|
|
|
func dismissSheet() { |
|
sheet = nil |
|
} |
|
|
|
func present(cover: Router) { |
|
self.cover = cover |
|
} |
|
|
|
func dismissCover() { |
|
cover = nil |
|
} |
|
|
|
func presentAlert(error: LocalizedError) { |
|
self.error = error |
|
hasError = true |
|
} |
|
|
|
@ViewBuilder |
|
func build(_ route: Router) -> some View { |
|
route.view() |
|
} |
|
} |
|
class TabCoordinator<Router: NavigationRouter>: ObservableObject { |
|
|
|
@Published var tab: Router |
|
|
|
init(tab: Router) { |
|
self.tab = tab |
|
} |
|
|
|
func change(_ route: Router) { |
|
self.tab = route |
|
} |
|
|
|
@ViewBuilder |
|
func build(_ route: Router) -> some View { |
|
route.view() |
|
.tag(route) |
|
} |
|
} |
Navigation in App |
Navigation from Widget |
CoordinatorDemonstration.mp4
|
Widget.route.mov
|
Coordinator
|
struct FruitsCoordinatorView: View { |
|
|
|
@StateObject var coordinator: Coordinator<FruitsRouter> |
|
|
|
var body: some View { |
|
NavigationStack(path: $coordinator.path) { |
|
coordinator.build(.fruits) |
|
.navigationTitle("Fruits ๐") |
|
.navigationDestination(for: FruitsRouter.self) { route in |
|
coordinator.build(route) |
|
} |
|
.sheet(item: $coordinator.sheet) { route in |
|
coordinator.build(route) |
|
} |
|
.fullScreenCover(item: $coordinator.cover) { route in |
|
coordinator.build(route) |
|
} |
|
} |
|
.environmentObject(coordinator) |
|
} |
|
} |
Router
|
enum FruitsRouter: NavigationRouter { |
|
case fruits |
|
case cherry(count: Int) |
|
case lemon(count: Int) |
|
case watermelon(count: Int) |
|
|
|
var id: Self { |
|
return self |
|
} |
|
|
|
var style: TransitionStyle? { |
|
return nil |
|
} |
|
|
|
@ViewBuilder |
|
func view() -> some View { |
|
switch self { |
|
case .fruits: |
|
FruitsView() |
|
case .cherry(let count): |
|
CherryView(count: count) |
|
case .lemon(let count): |
|
LemonView(count: count) |
|
case .watermelon(let count): |
|
WatermelonView(count: count) |
|
} |
|
} |
|
} |