Skip to content

Commit

Permalink
Merge branch 'navigation-beta' into prerelease/1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Feb 27, 2023
2 parents 053a851 + 44c1f87 commit c669dca
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 76 deletions.
15 changes: 8 additions & 7 deletions Sources/ComposableArchitecture/Internal/NavigationID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,20 @@ struct AnyID: Hashable, Identifiable, Sendable {

@usableFromInline
init<Base>(_ base: Base) {
func id(_ identifiable: some Identifiable) -> AnyHashableSendable {
func id<T: Identifiable>(_ identifiable: T) -> AnyHashableSendable {
AnyHashableSendable(identifiable.id)
}

self.objectIdentifier = ObjectIdentifier(Base.self)
self.tag = EnumMetadata(Base.self)?.tag(of: base)
if let base = base as? any Identifiable {
self.identifier = id(base)
} else if let metadata = EnumMetadata(type(of: base)),
metadata.associatedValueType(forTag: metadata.tag(of: base)) is any Identifiable.Type
{
// TODO: Extract enum payload and assign id
if let id = _id(base) {
self.identifier = AnyHashableSendable(id)
}
// TODO: Extract identifiable enum payload and assign id
// else if let metadata = EnumMetadata(type(of: base)),
// metadata.associatedValueType(forTag: metadata.tag(of: base)) is any Identifiable.Type
// {
// }
}

@usableFromInline
Expand Down
30 changes: 30 additions & 0 deletions Sources/ComposableArchitecture/Internal/OpenExistential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
self == other as? Self
}
}

// MARK: Identifiable

func _id(_ value: Any) -> AnyHashable? {
func open(_ value: some Identifiable) -> AnyHashable {
value.id
}
guard let value = value as? any Identifiable else { return nil }
return open(value)
}
#else
// MARK: -
// MARK: swift(<5.7)
Expand Down Expand Up @@ -39,4 +49,24 @@
return lhs == rhs
}
}

// MARK: Identifiable

func _id(_ value: Any) -> AnyHashable? {
func open<T>(_: T.Type) -> AnyHashable? {
(Witness<T>.self as? AnyIdentifiable.Type)?.id(value)
}
return _openExistential(type(of: value), do: open)
}

private protocol AnyIdentifiable {
static func id(_ value: Any) -> AnyHashable?
}

extension Witness: AnyIdentifiable where T: Identifiable {
fileprivate static func id(_ value: Any) -> AnyHashable? {
guard let value = value as? T else { return nil }
return value.id
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ extension ReducerProtocol {
file: StaticString = #file,
fileID: StaticString = #fileID,
line: UInt = #line
) -> _PresentationReducer<Self, EmptyReducer<DestinationState, DestinationAction>>
where DestinationState: _EphemeralState {
) -> _PresentationReducer<Self, EmptyReducer<DestinationState, DestinationAction>> {
self.ifLet(
toPresentationState,
action: toPresentationAction,
Expand Down
137 changes: 70 additions & 67 deletions Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift
Original file line number Diff line number Diff line change
@@ -1,78 +1,81 @@
import SwiftUI

extension View {
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
public func navigationDestination<State, Action, Destination: View>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
@ViewBuilder destination: @escaping (Store<State, Action>) -> Destination
) -> some View {
self.navigationDestination(
store: store, state: { $0 }, action: { $0 }, destination: destination
)
}
#if swift(>=5.7)
extension View {
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
public func navigationDestination<State, Action, Destination: View>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
@ViewBuilder destination: @escaping (Store<State, Action>) -> Destination
) -> some View {
self.navigationDestination(
store: store, state: { $0 }, action: { $0 }, destination: destination
)
}

@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
public func navigationDestination<
State, Action, DestinationState, DestinationAction, Destination: View
>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (State) -> DestinationState?,
action fromDestinationAction: @escaping (DestinationAction) -> Action,
@ViewBuilder destination: @escaping (Store<DestinationState, DestinationAction>) -> Destination
) -> some View {
self.modifier(
PresentationNavigationDestinationModifier(
store: store,
state: toDestinationState,
action: fromDestinationAction,
content: destination
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
public func navigationDestination<
State, Action, DestinationState, DestinationAction, Destination: View
>(
store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (State) -> DestinationState?,
action fromDestinationAction: @escaping (DestinationAction) -> Action,
@ViewBuilder destination: @escaping (Store<DestinationState, DestinationAction>) ->
Destination
) -> some View {
self.modifier(
PresentationNavigationDestinationModifier(
store: store,
state: toDestinationState,
action: fromDestinationAction,
content: destination
)
)
)
}
}
}

@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
private struct PresentationNavigationDestinationModifier<
State,
Action,
DestinationState,
DestinationAction,
CoverContent: View
>: ViewModifier {
let store: Store<PresentationState<State>, PresentationAction<Action>>
@StateObject var viewStore: ViewStore<Bool, PresentationAction<Action>>
let toDestinationState: (State) -> DestinationState?
let fromDestinationAction: (DestinationAction) -> Action
let coverContent: (Store<DestinationState, DestinationAction>) -> CoverContent
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
private struct PresentationNavigationDestinationModifier<
State,
Action,
DestinationState,
DestinationAction,
CoverContent: View
>: ViewModifier {
let store: Store<PresentationState<State>, PresentationAction<Action>>
@StateObject var viewStore: ViewStore<Bool, PresentationAction<Action>>
let toDestinationState: (State) -> DestinationState?
let fromDestinationAction: (DestinationAction) -> Action
let coverContent: (Store<DestinationState, DestinationAction>) -> CoverContent

init(
store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (State) -> DestinationState?,
action fromDestinationAction: @escaping (DestinationAction) -> Action,
content coverContent: @escaping (Store<DestinationState, DestinationAction>) -> CoverContent
) {
self.store = store
self._viewStore = StateObject(
wrappedValue: ViewStore(
store
.filter { state, _ in state.wrappedValue != nil }
.scope(state: { $0.wrappedValue.flatMap(toDestinationState) != nil })
init(
store: Store<PresentationState<State>, PresentationAction<Action>>,
state toDestinationState: @escaping (State) -> DestinationState?,
action fromDestinationAction: @escaping (DestinationAction) -> Action,
content coverContent: @escaping (Store<DestinationState, DestinationAction>) -> CoverContent
) {
self.store = store
self._viewStore = StateObject(
wrappedValue: ViewStore(
store
.filter { state, _ in state.wrappedValue != nil }
.scope(state: { $0.wrappedValue.flatMap(toDestinationState) != nil })
)
)
)
self.toDestinationState = toDestinationState
self.fromDestinationAction = fromDestinationAction
self.coverContent = coverContent
}
self.toDestinationState = toDestinationState
self.fromDestinationAction = fromDestinationAction
self.coverContent = coverContent
}

func body(content: Content) -> some View {
content.navigationDestination(isPresented: self.viewStore.binding(send: .dismiss)) {
IfLetStore(
self.store.scope(
state: returningLastNonNilValue { $0.wrappedValue.flatMap(self.toDestinationState) },
action: { .presented(self.fromDestinationAction($0)) }
),
then: self.coverContent
)
func body(content: Content) -> some View {
content.navigationDestination(isPresented: self.viewStore.binding(send: .dismiss)) {
IfLetStore(
self.store.scope(
state: returningLastNonNilValue { $0.wrappedValue.flatMap(self.toDestinationState) },
action: { .presented(self.fromDestinationAction($0)) }
),
then: self.coverContent
)
}
}
}
}
#endif

0 comments on commit c669dca

Please sign in to comment.