Skip to content

Commit

Permalink
Infer manualNavigation from routes, update example
Browse files Browse the repository at this point in the history
  • Loading branch information
josephktcheung committed Apr 26, 2022
1 parent 936ee2d commit 7901bda
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 96 deletions.
4 changes: 2 additions & 2 deletions FlowStacksApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
195 changes: 142 additions & 53 deletions FlowStacksApp/Shared/ManualCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<ManualScreen> = [.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<ManualScreen> = [.root(.details(0), embedInNavigationView: true)]

var randomRoutes: [Route<ManualScreen>] {
let options: [[Route<ManualScreen>]] = [
[.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()
}
}
68 changes: 29 additions & 39 deletions Sources/FlowStacks/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,52 +90,42 @@ indirect enum Node<Screen, V: View>: 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 }
)
}
}

Expand Down
30 changes: 28 additions & 2 deletions Sources/FlowStacks/RoutableCollection+utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
"""
Expand All @@ -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.
Expand Down

0 comments on commit 7901bda

Please sign in to comment.