From 7901bda666b5003c9a76e6875ac5a9baffbc6382 Mon Sep 17 00:00:00 2001 From: Joseph Cheung Date: Tue, 26 Apr 2022 16:39:09 +0800 Subject: [PATCH] Infer manualNavigation from routes, update example --- FlowStacksApp.xcodeproj/project.pbxproj | 4 +- FlowStacksApp/Shared/ManualCoordinator.swift | 195 +++++++++++++----- Sources/FlowStacks/Node.swift | 68 +++--- .../RoutableCollection+utilities.swift | 30 ++- 4 files changed, 201 insertions(+), 96 deletions(-) diff --git a/FlowStacksApp.xcodeproj/project.pbxproj b/FlowStacksApp.xcodeproj/project.pbxproj index 69eb204..c8b9bae 100644 --- a/FlowStacksApp.xcodeproj/project.pbxproj +++ b/FlowStacksApp.xcodeproj/project.pbxproj @@ -428,7 +428,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = QJAU94J65Z; + DEVELOPMENT_TEAM = 5R48U23HP6; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; @@ -457,7 +457,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = QJAU94J65Z; + DEVELOPMENT_TEAM = 5R48U23HP6; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; diff --git a/FlowStacksApp/Shared/ManualCoordinator.swift b/FlowStacksApp/Shared/ManualCoordinator.swift index 700c048..dd7eee7 100644 --- a/FlowStacksApp/Shared/ManualCoordinator.swift +++ b/FlowStacksApp/Shared/ManualCoordinator.swift @@ -3,78 +3,167 @@ import SwiftUI import SwiftUINavigation enum ManualScreen: Equatable { - case details(Int) - case contactUs + case details(Int) + case contactUs } struct SideBar: View { - var onTap: (Int) -> Void - - var body: some View { - List(1...10, id: \.self) { index in - Text("Item \(index)") - .onTapGesture { - onTap(index) - } + var pushNative: () -> Void + var pushManual: () -> Void + + var body: some View { + List { + Text("Native NavigationView") + .onTapGesture { + pushNative() + } + Text("Manual Navigation") + .onTapGesture { + pushManual() } } + } } struct ContactUsView: View { - var body: some View { - Text("Contact us") + var goBack: (() -> Void)? + var body: some View { + VStack { + Text("Contact us") + if let goBack = goBack { + Button("Back", action: goBack) + } } + } } struct DetailsView: View { - var number: Int = 0 - var onTapContact: () -> Void - var goToNext: (Int) -> Void - var goBack: () -> Void - var body: some View { - VStack { - Text("Item \(number)") - Button("Contact us") { - onTapContact() - } - Button("Push next") { - goToNext(number + 1) - } - Button("Go back", action: goBack) - } + @Binding var number: Int + var pushContact: () -> Void + let presentDoubleCover: (Int) -> Void + let presentDoubleSheet: (Int) -> Void + let pushNext: (Int) -> Void + let goBack: (() -> Void)? + let goBackToRoot: () -> Void + let goRandom: (() -> Void)? + + var body: some View { + VStack { + NumberView( + number: $number, + presentDoubleCover: presentDoubleCover, + presentDoubleSheet: presentDoubleSheet, + pushNext: pushNext, + goBack: goBack, + goBackToRoot: goBackToRoot, + goRandom: goRandom + ) + Button("Contact us", action: pushContact) } + } } struct ManualCoordinator: View { - @State var routes: Routes = [.root(.details(0), embedInNavigationView: false, manualNavigation: true)] - - var body: some View { - HStack { - SideBar { index in - routes = [.root(.details(index), embedInNavigationView: false, manualNavigation: true)] - }.frame(width: 300) - Router($routes) { screen, index in - switch screen { - case .details(let number): - DetailsView(number: number) { - routes.push(.contactUs, manualNavigation: true) - } goToNext: { newNum in - routes.push(.details(newNum), manualNavigation: true) - } goBack: { - if index != 0 { - routes.goBack() - } - } - case .contactUs: - ContactUsView() - } - }.frame(maxWidth: .infinity) + @State var routes: Routes = [.root(.details(0), embedInNavigationView: true)] + + var randomRoutes: [Route] { + let options: [[Route]] = [ + [.root(.details(0), manualNavigation: true)], + [ + .root(.details(0), manualNavigation: true), + .push(.details(1), manualNavigation: true), + .push(.details(2), manualNavigation: true), + .push(.details(3), manualNavigation: true), + .sheet(.details(4), embedInNavigationView: false, manualNavigation: true), + .push(.details(5), manualNavigation: true) + ], + [ + .root(.details(0), manualNavigation: true), + .push(.details(1), manualNavigation: true), + .push(.details(2), manualNavigation: true), + .push(.details(3), manualNavigation: true) + ], + [ + .root(.details(0), manualNavigation: true), + .push(.details(1), manualNavigation: true), + .sheet(.details(2), embedInNavigationView: false, manualNavigation: true), + .push(.details(3), manualNavigation: true), + .sheet(.details(4), embedInNavigationView: true), + .push(.details(5)) + ], + [ + .root(.details(0), manualNavigation: true), + .sheet(.details(1), embedInNavigationView: true), + .cover(.details(2), embedInNavigationView: true), + .push(.details(3)), + .sheet(.details(4), embedInNavigationView: true), + .push(.details(5)) + ] + ] + return options.randomElement()! + } + + var body: some View { + HStack { + SideBar( + pushNative: { + routes = [.root(.details(0), embedInNavigationView: true)] + }, + pushManual: { + routes = [.root(.details(0), manualNavigation: true)] } + ).frame(width: 300) + VStack { + Router($routes) { $screen, index in + switch screen { + case .details: + if let number = Binding(unwrapping: $screen, case: /ManualScreen.details) { + DetailsView( + number: number, + pushContact: { + routes.push(.contactUs) + }, + presentDoubleCover: { number in + #if os(macOS) + routes.presentSheet(.details(number * 2), manualNavigation: true) + #else + routes.presentSheet(.details(number * 2), embedInNavigationView: true) + #endif + }, + presentDoubleSheet: { number in + #if os(macOS) + routes.presentSheet(.details(number * 2), manualNavigation: true) + #else + routes.presentSheet(.details(number * 2), embedInNavigationView: true) + #endif + }, + pushNext: { number in + routes.push(.details(number + 1)) + }, + goBack: index != 0 ? { routes.goBack() } : nil, + goBackToRoot: { + $routes.withDelaysIfUnsupported { + $0.goBackToRoot() + } + }, + goRandom: { + $routes.withDelaysIfUnsupported { + $0 = randomRoutes + } + } + ) + } + case .contactUs: + ContactUsView(goBack: index != 0 ? { routes.goBack() } : nil) + } + }.frame(maxWidth: .infinity, maxHeight: .infinity) + } } + } } struct ManualCoordinator_Previews: PreviewProvider { - static var previews: some View { - ManualCoordinator() - } + static var previews: some View { + ManualCoordinator() + } } diff --git a/Sources/FlowStacks/Node.swift b/Sources/FlowStacks/Node.swift index 0ea2cfd..46836aa 100644 --- a/Sources/FlowStacks/Node.swift +++ b/Sources/FlowStacks/Node.swift @@ -90,52 +90,42 @@ indirect enum Node: View { } @ViewBuilder - private var manualNavigationBody: some View { - if #available(iOS 14.5, *) { + private var manualNavigationView: some View { + switch next { + case .route(.push(_, manualNavigation: true), _, _, _, _), .route(.sheet(_, _, manualNavigation: true, _), _, _, _, _): if isActiveBinding.wrappedValue { next - .sheet( - isPresented: sheetBinding, - onDismiss: onDismiss, - content: { next } - ) - .cover( - isPresented: coverBinding, - onDismiss: onDismiss, - content: { next } - ) } else { screenView - .sheet( - isPresented: sheetBinding, - onDismiss: onDismiss, - content: { next } - ) - .cover( - isPresented: coverBinding, - onDismiss: onDismiss, - content: { next } - ) } + default: + screenView + } + } + + @ViewBuilder + private var manualNavigationBody: some View { + if #available(iOS 14.5, *) { + manualNavigationView + .sheet( + isPresented: sheetBinding, + onDismiss: onDismiss, + content: { next } + ) + .cover( + isPresented: coverBinding, + onDismiss: onDismiss, + content: { next } + ) } else { let asSheet = next?.route?.style.isSheet ?? false - if isActiveBinding.wrappedValue { - next - .present( - asSheet: asSheet, - isPresented: asSheet ? sheetBinding : coverBinding, - onDismiss: onDismiss, - content: { next } - ) - } else { - screenView - .present( - asSheet: asSheet, - isPresented: asSheet ? sheetBinding : coverBinding, - onDismiss: onDismiss, - content: { next } - ) - } + manualNavigationView + .present( + asSheet: asSheet, + isPresented: asSheet ? sheetBinding : coverBinding, + onDismiss: onDismiss, + content: { next } + ) } } diff --git a/Sources/FlowStacks/RoutableCollection+utilities.swift b/Sources/FlowStacks/RoutableCollection+utilities.swift index b7dbf75..59cb012 100644 --- a/Sources/FlowStacks/RoutableCollection+utilities.swift +++ b/Sources/FlowStacks/RoutableCollection+utilities.swift @@ -30,11 +30,37 @@ public extension RoutableCollection where Element: RouteProtocol { } return nil } + + var isManualNavigation: Bool? { + for (index, route) in zip(indices, self).reversed() { + switch route.style { + case .push: + continue + case .cover(let embedInNavigationView): + if index > 0 { + return embedInNavigationView + } else { + // Once we reach the root screen, it's not possible to determine if the routes are being pushed onto a + // parent coordinator with a NavigationView, so we return nil rather than false. + return embedInNavigationView ? false : nil + } + case .sheet(_, let manualNavigation): + if index > 0 { + return manualNavigation + } else { + // Once we reach the root screen, it's not possible to determine if the routes are being pushed onto a + // parent coordinator with a NavigationView, so we return nil rather than false. + return manualNavigation ? true : nil + } + } + } + return nil + } /// Pushes a new screen via a push navigation. /// This should only be called if the most recently presented screen is embedded in a `NavigationView`. /// - Parameter screen: The screen to push. - mutating func push(_ screen: Element.Screen, manualNavigation: Bool = false) { + mutating func push(_ screen: Element.Screen) { assert( canPush != false, """ @@ -43,7 +69,7 @@ public extension RoutableCollection where Element: RouteProtocol { route has `embedInNavigationView` set to `true`. """ ) - _append(element: .push(screen, manualNavigation: manualNavigation)) + _append(element: .push(screen, manualNavigation: isManualNavigation ?? false)) } /// Presents a new screen via a sheet presentation.