此软件包是 SNAP 套件的一部分。

SnapNavigation

定义 SwiftUI 应用的导航结构,并使其与其呈现方式解耦。

Documentation

SnapNavigation 允许您以通用的方式定义应用程序的导航层次结构。可以通过选择、推送(在选定的或当前模态堆栈上)或模态呈现来显示屏幕。它还允许您深度链接到特定的屏幕,同时设置整个层次结构。

该软件包提供 SnapNavigationDestination 来定义屏幕,并提供 SnapNavigationProvider 来定义如何在它们之间导航。

在您的 App 定义中使用 SnapNavigationApp,以便 SnapNavigation 处理呈现和窗口管理。它通过 Environment 提供一个处理程序 \.navigator,以触发 NavigatorAction。它支持不同的呈现样式,如选项卡或单页,可以动态更改,而不会丢失导航状态。

Window = 应用窗口 Scene = NavigationStack Destination = 屏幕

支持

// TODO: 键盘导航,更好的辅助功能支持,侧边栏重新排序

演示项目

演示项目 显示了一个具有 3 个顶级项目的导航层次结构,可供选择。它允许无限的项目被推送或呈现为模态,并且有一些深度链接可以导航到更复杂的状态。

如何使用

定义 App 可以导航到的目标

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

实现一个 SnapNavigationProvider 来定义可访问目标的结构

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
		}
	}
}

在您的 @main App 定义中使用 SnapNavigationApp

@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().

### macOS: TabView sidebarAdaptable clicking label does not select
Happening since macOS 15.1 [FB15680632](https://github.com/simonnickel/FB15680632-SwiftUImacOS-TabView-sidebarAdaptable-labelNotSelectable)
// TODO FB15680632: Check if issue is solved

### 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: 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).

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