import OrderedCollections import SwiftUI /// A navigation stack that is driven by a store. /// /// This view can be used to drive stack-based navigation in the Composable Architecture when passed /// a store that is focused on ``StackState`` and ``StackAction``. /// /// See the dedicated article on for more information on the library's navigation /// tools, and in particular see for information on using this view. @available( iOS, deprecated: 9999, message: "Use 'NavigationStack.init(path:)' with a store scoped from observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-NavigationStackStore-with-NavigationStack]" ) @available( macOS, deprecated: 9999, message: "Use 'NavigationStack.init(path:)' with a store scoped from observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-NavigationStackStore-with-NavigationStack]" ) @available( tvOS, deprecated: 9999, message: "Use 'NavigationStack.init(path:)' with a store scoped from observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-NavigationStackStore-with-NavigationStack]" ) @available( watchOS, deprecated: 9999, message: "Use 'NavigationStack.init(path:)' with a store scoped from observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-NavigationStackStore-with-NavigationStack]" ) @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) public struct NavigationStackStore: View { private let root: Root private let destination: (StackState.Component) -> Destination @ObservedObject private var viewStore: ViewStore, StackAction> /// Creates a navigation stack with a store of stack state and actions. /// /// - Parameters: /// - store: A store of stack state and actions to power this stack. /// - root: The view to display when the stack is empty. /// - destination: A view builder that defines a view to display when an element is appended to /// the stack's state. The closure takes one argument, which is a store of the value to /// present. /// - fileID: The fileID. /// - filePath: The filePath. /// - line: The line. /// - column: The column. public init( _ store: Store, StackAction>, @ViewBuilder root: () -> Root, @ViewBuilder destination: @escaping (_ store: Store) -> Destination, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column ) { func navigationDestination( component: StackState.Component ) -> Destination { let id = store.id( state: \.[ id: component.id, fileID: _HashableStaticString(rawValue: fileID), filePath: _HashableStaticString(rawValue: filePath), line: line, column: column ], action: \.[id: component.id] ) @MainActor func open( _ core: some Core, StackAction> ) -> any Core { IfLetCore( base: core, cachedState: component.element, stateKeyPath: \.[ id: component.id, fileID: _HashableStaticString(rawValue: fileID), filePath: _HashableStaticString(rawValue: filePath), line: line, column: column ], actionKeyPath: \.[id: component.id] ) } return destination(store.scope(id: id, childCore: open(store.core))) } self.root = root() self.destination = navigationDestination(component:) self._viewStore = ObservedObject( wrappedValue: ViewStore( store, observe: { $0 }, removeDuplicates: { areOrderedSetsDuplicates($0.ids, $1.ids) } ) ) } /// Creates a navigation stack with a store of stack state and actions. /// /// - Parameters: /// - store: A store of stack state and actions to power this stack. /// - root: The view to display when the stack is empty. /// - destination: A view builder that defines a view to display when an element is appended to /// the stack's state. The closure takes one argument, which is the initial enum state to /// present. You can switch over this value and use ``CaseLet`` views to handle each case. /// - fileID: The fileID. /// - filePath: The filePath. /// - line: The line. /// - column: The column. @_disfavoredOverload public init( _ store: Store, StackAction>, @ViewBuilder root: () -> Root, @ViewBuilder destination: @escaping (_ initialState: State) -> D, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column ) where Destination == SwitchStore { func navigationDestination( component: StackState.Component ) -> Destination { let id = store.id( state: \.[ id: component.id, fileID: _HashableStaticString(rawValue: fileID), filePath: _HashableStaticString(rawValue: filePath), line: line, column: column ], action: \.[id: component.id] ) if let child = store.children[id] as? Store { return SwitchStore(child, content: destination) } else { @MainActor func open( _ core: some Core, StackAction> ) -> any Core { IfLetCore( base: core, cachedState: component.element, stateKeyPath: \.[ id: component.id, fileID: _HashableStaticString(rawValue: fileID), filePath: _HashableStaticString(rawValue: filePath), line: line, column: column ], actionKeyPath: \.[id: component.id] ) } return SwitchStore(store.scope(id: id, childCore: open(store.core)), content: destination) } } self.root = root() self.destination = navigationDestination(component:) self._viewStore = ObservedObject( wrappedValue: ViewStore( store, observe: { $0 }, removeDuplicates: { areOrderedSetsDuplicates($0.ids, $1.ids) } ) ) } public var body: some View { NavigationStack( path: self.viewStore.binding( get: { $0.path }, compactSend: { newPath in if newPath.count > self.viewStore.path.count, let component = newPath.last { return .push(id: component.id, state: component.element) } else if newPath.count < self.viewStore.path.count { return .popFrom(id: self.viewStore.path[newPath.count].id) } else { return nil } } ) ) { self.root .environment(\.navigationDestinationType, State.self) .navigationDestination(for: StackState.Component.self) { component in NavigationDestinationView(component: component, destination: self.destination) } } } } private struct NavigationDestinationView: View { let component: StackState.Component let destination: (StackState.Component) -> Destination var body: some View { self.destination(self.component) .environment(\.navigationDestinationType, State.self) .id(self.component.id) } }