Skip to content

Commit

Permalink
Merge pull request #39 from SwiftfulThinking/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
SwiftfulThinking committed Jan 17, 2024
2 parents 59c2752 + a9cbde6 commit f32b6fb
Show file tree
Hide file tree
Showing 12 changed files with 818 additions and 162 deletions.
15 changes: 12 additions & 3 deletions Sources/SwiftfulRouting/Components/NavigationViewIfNeeded.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ struct NavigationViewIfNeeded<Content:View>: View {

let addNavigationView: Bool
let segueOption: SegueOption
let onDismissCurrentPush: (() -> Void)?
let onDismissLastPush: () -> Void
@Binding var screens: [AnyDestination]
@ViewBuilder var content: Content

@ViewBuilder var body: some View {
if addNavigationView {
if #available(iOS 16.0, *) {
NavigationStackTransformable(segueOption: segueOption, screens: $screens) {
NavigationStackTransformable(segueOption: segueOption, onDismissLastPush: onDismissLastPush, screens: $screens) {
content
}
} else {
Expand All @@ -27,7 +29,13 @@ struct NavigationViewIfNeeded<Content:View>: View {
}
}
} else {
content
if #available(iOS 16.0, *) {
// onChangeOfPresentationMode is NOT required for iOS 16 bc onDismiss will trigger within NavigationStackTransformable
content
} else {
content
.onChangeOfPresentationMode(screens: $screens, onDismiss: onDismissCurrentPush)
}
}
}
}
Expand All @@ -42,6 +50,7 @@ struct NavigationStackTransformable<Content:View>: View {
// We have to observe the path to monitor native screen dismissal that aren't via router.dismiss

let segueOption: SegueOption
let onDismissLastPush: () -> Void
@Binding var screens: [AnyDestination]
@ViewBuilder var content: Content

Expand All @@ -65,7 +74,7 @@ struct NavigationStackTransformable<Content:View>: View {
}
.onChange(of: path, perform: { path in
if path.count < screens.count {
screens.removeLast()
onDismissLastPush()
}
})
}
Expand Down
122 changes: 109 additions & 13 deletions Sources/SwiftfulRouting/Core/AnyRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,82 @@
import Foundation
import SwiftUI

public struct RouterEnvironmentKey: EnvironmentKey {
public static let defaultValue: AnyRouter = AnyRouter(object: MockRouter())
}

public extension EnvironmentValues {
var router: AnyRouter {
get { self[RouterEnvironmentKey.self] }
set { self[RouterEnvironmentKey.self] = newValue }
}
}

/// Type-erased Router with convenience methods.
public struct AnyRouter: Router {
private let object: any Router

public init(object: any Router) {
self.object = object
}

public var screens: [AnyDestination] {
object.screens
/// Show any screen via Push (NavigationLink), Sheet, or FullScreenCover.
public func showScreen<T>(_ option: SegueOption, onDismiss: (() -> Void)? = nil, @ViewBuilder destination: @escaping (AnyRouter) -> T) where T : View {
object.enterScreenFlow([AnyRoute(option, onDismiss: onDismiss, destination: destination)])
}

/// Show any screen via Push (NavigationLink), Sheet, or FullScreenCover.
public func showScreen<T>(_ option: SegueOption, @ViewBuilder destination: @escaping (AnyRouter) -> T) where T : View {
object.showScreen(option, destination: destination)
public func showScreen(_ route: AnyRoute) {
object.enterScreenFlow([route])
}

/// Show a flow of screens, segueing to the first route immediately. The following routes can be accessed via 'showNextScreen()'.
public func enterScreenFlow(_ routes: [AnyRoute]) {
object.enterScreenFlow(routes)
}

/// Shows the next screen set in the current screen flow. This would have been set previously via showScreens().
public func showNextScreen() throws {
try object.showNextScreen()
}

/// If there is a next screen in the current screen flow, go to it. Otherwise, flow is complete and dismiss the environment.
public func showNextScreenOrDismissEnvironment() {
do {
try showNextScreen()
} catch {
dismissEnvironment()
}
}

/// Dismiss the top-most presented environment (this would be the top-most sheet or fullScreenCover).
public func dismissEnvironment() {
object.dismissEnvironment()
}

/// Dismiss the top-most presented screen in the current Environment. Same as calling presentationMode.wrappedValue.dismiss().
public func dismissScreen() {
object.dismissScreen()
}

/// Dismiss all NavigationLinks in NavigationStack heirarchy.
/// Push a stack of screens and show the last one immediately.
@available(iOS 16, *)
public func pushScreens(destinations: [(AnyRouter) -> any View]) {
object.pushScreens(destinations: destinations)
public func pushScreenStack(destinations: [PushRoute]) {
object.pushScreenStack(destinations: destinations)
}

/// Show a resizeable sheet on top of the current context.
@available(iOS 16, *)
public func showResizableSheet<V>(sheetDetents: Set<PresentationDetentTransformable>, selection: Binding<PresentationDetentTransformable>?, showDragIndicator: Bool, destination: @escaping (AnyRouter) -> V) where V : View {
object.showResizableSheet(sheetDetents: sheetDetents, selection: selection, showDragIndicator: showDragIndicator, destination: destination)
public func showResizableSheet<V>(sheetDetents: Set<PresentationDetentTransformable>, selection: Binding<PresentationDetentTransformable>?, showDragIndicator: Bool, onDismiss: (() -> Void)? = nil, destination: @escaping (AnyRouter) -> V) where V : View {
object.showResizableSheet(sheetDetents: sheetDetents, selection: selection, showDragIndicator: showDragIndicator, onDismiss: onDismiss, destination: destination)
}

/// Dismiss all NavigationLinks in NavigationStack heirarchy.
///
/// WARNING: Does not dismiss Sheet or FullScreenCover.
@available(iOS 16, *)
public func popToRoot() {
object.popToRoot()
public func dismissScreenStack() {
object.dismissScreenStack()
}

/// Show any Alert or ConfirmationDialog.
Expand Down Expand Up @@ -108,3 +145,62 @@ public struct AnyRouter: Router {
}

}

struct MockRouter: Router {

private func printError() {
#if DEBUG
print("Routing failure: Attempt to use AnyRouter without first adding RouterView to the View heirarchy!")
#endif
}

func enterScreenFlow(_ routes: [AnyRoute]) {
printError()
}

func showNextScreen() throws {
printError()
}

func dismissScreen() {
printError()
}

func dismissEnvironment() {
printError()
}

func dismissScreenStack() {
printError()
}

func pushScreenStack(destinations: [PushRoute]) {
printError()
}

func showResizableSheet<V>(sheetDetents: Set<PresentationDetentTransformable>, selection: Binding<PresentationDetentTransformable>?, showDragIndicator: Bool, onDismiss: (() -> Void)?, destination: @escaping (AnyRouter) -> V) where V : View {
printError()
}

func showAlert<T>(_ option: AlertOption, title: String, subtitle: String?, alert: @escaping () -> T, buttonsiOS13: [Alert.Button]?) where T : View {
printError()
}

func dismissAlert() {
printError()
}

func showModal<V>(transition: AnyTransition, animation: Animation, alignment: Alignment, backgroundColor: Color?, backgroundEffect: BackgroundEffect?, useDeviceBounds: Bool, destination: @escaping () -> V) where V : View {
printError()
}

func dismissModal() {
printError()
}

func showSafari(_ url: @escaping () -> URL) {
printError()
}


}
13 changes: 7 additions & 6 deletions Sources/SwiftfulRouting/Core/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@ import SwiftUI
import Combine

public protocol Router {
var screens: [AnyDestination] { get }
func showScreen<V:View>(_ option: SegueOption, @ViewBuilder destination: @escaping (AnyRouter) -> V)
func enterScreenFlow(_ routes: [AnyRoute])
func showNextScreen() throws
func dismissScreen()
func dismissEnvironment()
@available(iOS 16, *)
func dismissScreenStack()

@available(iOS 16, *)
func pushScreens(destinations: [(AnyRouter) -> any View])
func pushScreenStack(destinations: [PushRoute])

@available(iOS 16, *)
func popToRoot()

@available(iOS 16, *)
func showResizableSheet<V:View>(sheetDetents: Set<PresentationDetentTransformable>, selection: Binding<PresentationDetentTransformable>?, showDragIndicator: Bool, @ViewBuilder destination: @escaping (AnyRouter) -> V)
func showResizableSheet<V:View>(sheetDetents: Set<PresentationDetentTransformable>, selection: Binding<PresentationDetentTransformable>?, showDragIndicator: Bool, onDismiss: (() -> Void)?, @ViewBuilder destination: @escaping (AnyRouter) -> V)

func showAlert<T:View>(_ option: AlertOption, title: String, subtitle: String?, @ViewBuilder alert: @escaping () -> T, buttonsiOS13: [Alert.Button]?)

Expand Down
Loading

0 comments on commit f32b6fb

Please sign in to comment.