import Combine import SwiftUI /// A view that safely unwraps a store of optional state in order to show one of two views. /// /// When the underlying state is non-`nil`, the `then` closure will be performed with a ``Store`` /// that holds onto non-optional state, and otherwise the `else` closure will be performed. /// /// This is useful for deciding between two views to show depending on an optional piece of state: /// /// ```swift /// IfLetStore( /// store.scope(state: \.results, action: { .results($0) }) /// ) { /// SearchResultsView(store: $0) /// } else: { /// Text("Loading search results...") /// } /// ``` /// @available( iOS, deprecated: 9999, message: "Use 'if let' with a store of observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-IfLetStore-with-if-let]" ) @available( macOS, deprecated: 9999, message: "Use 'if let' with a store of observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-IfLetStore-with-if-let]" ) @available( tvOS, deprecated: 9999, message: "Use 'if let' with a store of observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-IfLetStore-with-if-let]" ) @available( watchOS, deprecated: 9999, message: "Use 'if let' with a store of observable state, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-IfLetStore-with-if-let]" ) public struct IfLetStore: View { private let content: (ViewStore) -> Content private let store: Store /// Initializes an ``IfLetStore`` view that computes content depending on if a store of optional /// state is `nil` or non-`nil`. /// /// - Parameters: /// - store: A store of optional state. /// - ifContent: A function that is given a store of non-optional state and returns a view that /// is visible only when the optional state is non-`nil`. /// - elseContent: A view that is only visible when the optional state is `nil`. #if swift(<5.10) @MainActor(unsafe) #else @preconcurrency@MainActor #endif public init( _ store: Store, @ViewBuilder then ifContent: @escaping (_ store: Store) -> IfContent, @ViewBuilder else elseContent: () -> ElseContent ) where Content == _ConditionalContent { func open(_ core: some Core) -> any Core { _IfLetCore(base: core) } let store = store.scope( id: store.id(state: \.self, action: \.self), childCore: open(store.core) ) self.store = store let elseContent = elseContent() self.content = { viewStore in if let state = viewStore.state { @MainActor func open(_ core: some Core) -> any Core { IfLetCore(base: core, cachedState: state, stateKeyPath: \.self, actionKeyPath: \.self) } return ViewBuilder.buildEither( first: ifContent( store.scope( id: store.id(state: \.!, action: \.self), childCore: open(store.core) ) ) ) } else { return ViewBuilder.buildEither(second: elseContent) } } } /// Initializes an ``IfLetStore`` view that computes content depending on if a store of optional /// state is `nil` or non-`nil`. /// /// - Parameters: /// - store: A store of optional state. /// - ifContent: A function that is given a store of non-optional state and returns a view that /// is visible only when the optional state is non-`nil`. #if swift(<5.10) @MainActor(unsafe) #else @preconcurrency@MainActor #endif public init( _ store: Store, @ViewBuilder then ifContent: @escaping (_ store: Store) -> IfContent ) where Content == IfContent? { func open(_ core: some Core) -> any Core { _IfLetCore(base: core) } let store = store.scope( id: store.id(state: \.self, action: \.self), childCore: open(store.core) ) self.store = store self.content = { viewStore in if let state = viewStore.state { @MainActor func open(_ core: some Core) -> any Core { IfLetCore(base: core, cachedState: state, stateKeyPath: \.self, actionKeyPath: \.self) } return ifContent( store.scope( id: store.id(state: \.!, action: \.self), childCore: open(store.core) ) ) } else { return nil } } } /// Initializes an ``IfLetStore`` view that computes content depending on if a store of /// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil`. /// /// - Parameters: /// - store: A store of optional state. /// - ifContent: A function that is given a store of non-optional state and returns a view that /// is visible only when the optional state is non-`nil`. /// - elseContent: A view that is only visible when the optional state is `nil`. @available( iOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) @available( macOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) @available( tvOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) @available( watchOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) #if swift(<5.10) @MainActor(unsafe) #else @preconcurrency@MainActor #endif public init( _ store: Store, PresentationAction>, @ViewBuilder then ifContent: @escaping (_ store: Store) -> IfContent, @ViewBuilder else elseContent: @escaping () -> ElseContent ) where Content == _ConditionalContent { self.init( store.scope(state: \.wrappedValue, action: \.presented), then: ifContent, else: elseContent ) } /// Initializes an ``IfLetStore`` view that computes content depending on if a store of /// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil`. /// /// - Parameters: /// - store: A store of optional state. /// - ifContent: A function that is given a store of non-optional state and returns a view that /// is visible only when the optional state is non-`nil`. @available( iOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) @available( macOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) @available( tvOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) @available( watchOS, deprecated: 9999, message: "Scope the store into the destination's wrapped 'state' and presented 'action', instead: 'store.scope(state: \\.destination, action: \\.destination.presented)'. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) #if swift(<5.10) @MainActor(unsafe) #else @preconcurrency@MainActor #endif public init( _ store: Store, PresentationAction>, @ViewBuilder then ifContent: @escaping (_ store: Store) -> IfContent ) where Content == IfContent? { self.init( store.scope(state: \.wrappedValue, action: \.presented), then: ifContent ) } /// Initializes an ``IfLetStore`` view that computes content depending on if a store of /// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil` and state can further /// be extracted from the destination state, _e.g._ it matches a particular case of an enum. /// /// - Parameters: /// - store: A store of optional state. /// - toState: A closure that attempts to extract state for the "if" branch from the destination /// state. /// - fromAction: A closure that embeds actions for the "if" branch in destination actions. /// - ifContent: A function that is given a store of non-optional state and returns a view that /// is visible only when the optional state is non-`nil` and state can be extracted from the /// destination state. /// - elseContent: A view that is only visible when state cannot be extracted from the /// destination. @available( *, deprecated, message: "Further scope the store into the 'state' and 'action' cases, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) #if swift(<5.10) @MainActor(unsafe) #else @preconcurrency@MainActor #endif public init( _ store: Store, PresentationAction>, state toState: @escaping (_ destinationState: DestinationState) -> State?, action fromAction: @escaping (_ action: Action) -> DestinationAction, @ViewBuilder then ifContent: @escaping (_ store: Store) -> IfContent, @ViewBuilder else elseContent: @escaping () -> ElseContent ) where Content == _ConditionalContent { self.init( store.scope( state: { $0.wrappedValue.flatMap(toState) }, action: { .presented(fromAction($0)) } ), then: ifContent, else: elseContent ) } /// Initializes an ``IfLetStore`` view that computes content depending on if a store of /// ``PresentationState`` and ``PresentationAction`` is `nil` or non-`nil` and state can further /// be extracted from the destination state, _e.g._ it matches a particular case of an enum. /// /// - Parameters: /// - store: A store of optional state. /// - toState: A closure that attempts to extract state for the "if" branch from the destination /// state. /// - fromAction: A closure that embeds actions for the "if" branch in destination actions. /// - ifContent: A function that is given a store of non-optional state and returns a view that /// is visible only when the optional state is non-`nil` and state can be extracted from the /// destination state. @available( *, deprecated, message: "Further scope the store into the 'state' and 'action' cases, instead. For more information, see the following article: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs" ) #if swift(<5.10) @MainActor(unsafe) #else @preconcurrency@MainActor #endif public init( _ store: Store, PresentationAction>, state toState: @escaping (_ destinationState: DestinationState) -> State?, action fromAction: @escaping (_ action: Action) -> DestinationAction, @ViewBuilder then ifContent: @escaping (_ store: Store) -> IfContent ) where Content == IfContent? { self.init( store.scope( state: { $0.wrappedValue.flatMap(toState) }, action: { .presented(fromAction($0)) } ), then: ifContent ) } public var body: some View { WithViewStore( self.store, observe: { $0 }, removeDuplicates: { ($0 != nil) == ($1 != nil) }, content: self.content ) } } private final class _IfLetCore, Wrapped, Action>: Core { let base: Base init(base: Base) { self.base = base } var state: Base.State { base.state } func send(_ action: Action) -> Task? { base.send(action) } var canStoreCacheChildren: Bool { base.canStoreCacheChildren } var didSet: CurrentValueRelay { base.didSet } var isInvalid: Bool { state == nil || base.isInvalid } var effectCancellables: [UUID: AnyCancellable] { base.effectCancellables } }