Contents

simonnickel/snap-navigation

> This package is part of the [SNAP](https://github.com/simonnickel/snap) suite.

Demo project

The demo project shows a navigation hierarchy with 3 top level items to select. It allows infinite items to be pushed or presented as modals and a few deeplinks to navigate to a more complex state.

<img src="/screenshot.png" height="400">

How to use

Define Destinations the App can navigate to:

enum Destination: SnapNavigationDestination {		
	case triangle, rectangle, circle
	
	var definition: SnapNavigation.ScreenDefinition<Self> {
		switch self {
			case .triangle: .init(title: "Triangle", systemIcon: "triangle")
			...
		}
	}
}

Implement a SnapNavigationProvider to define the structure of reachable destinations:

struct NavigationProvider: SnapNavigationProvider {
	var initialSelection: Destination { .triangle }
	
	var selectableDestinations: [Destination] { [.triangle, .rectangle, .circle] }
	
	func parent(of destination: Destination) -> Destination? {
		switch destination {
			case .triangle, .rectangle, .circle: nil
		}
	}
}

Use SnapNavigationApp in your @main App definition:

@main
struct SnapNavigationDemoApp: App {
	
    var body: some Scene {
		
        SnapNavigationApp(provider: NavigationProvider()) { window, content in
			content
				.navigationStyle(.single)
				// ... setup more global stuff ... 
		}
		
    }
	
}```

Use `\.navigator` to navigate in your App:

@Environment(\.navigator) private var navigator

navigator(.present(Destination.infinity))

Considerations

TabSection

iOS 18 supports to group multiple Tabs into a TabSection: While the sidebar is visible, the Tabs are visible below the section header. While the TabBar is visible, only the section header is visible as a tab.

This causes ambiguous state when switching size classes or hiding the sidebar. I tried a few things, like manually adding the Section on the NavigationStack. But was not really happy with any of them.

Decision: Not supporting TabSection for now.

.fullScreenCover()

Supporting a mix of .sheet() and .fullScreenCover() causes some animation issues in deeplink handling.

Decision: Not supporting .fullScreenCover() for now. Modal presentation uses .sheet().

// TODO feature: Define FullScreenCover as additional PresentationStyle, which can only be present once as last item with a path to show, no modals. (Or even with its own complete SnapNavigationView and State).

macOS: TabView with .sidebarAdaptable does not maintain state of Tab / Sidebar Item.

Decision: Did not find a way to maintain the navigation state, not worth it at the moment. Reconsider in the future.

AppDestinations + Navigator: Navigator Actions via Environment do not support a generic constraint to a Destination Type.

Decision: Implementation is possible, but causes a lot of boilerplate and redundancy. Left it out for now to encourage using different Destination enums for Features.

// TODO: Fix tapping in background when 2 modals are open closes all modals. (on iPad)

Package Metadata

Repository: simonnickel/snap-navigation

Default branch: main

README: README.md