Skip to content

Commit 961be05

Browse files
committed
Backport navigationDestination bug fix from SwiftUINavigation
1 parent 3954b4a commit 961be05

File tree

3 files changed

+86
-77
lines changed

3 files changed

+86
-77
lines changed

ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let package = Package(
2525
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "0.9.1"),
2626
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.2.0"),
2727
.package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.7.0"),
28-
.package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.7.0"),
28+
.package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.7.1"),
2929
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.8.4"),
3030
],
3131
targets: [
@@ -39,6 +39,7 @@ let package = Package(
3939
.product(name: "IdentifiedCollections", package: "swift-identified-collections"),
4040
.product(name: "OrderedCollections", package: "swift-collections"),
4141
.product(name: "_SwiftUINavigationState", package: "swiftui-navigation"),
42+
.product(name: "SwiftUINavigation", package: "swiftui-navigation"),
4243
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
4344
]
4445
),
Lines changed: 82 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,93 @@
11
import SwiftUI
2+
import SwiftUINavigation
23

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

15-
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
16-
public func navigationDestination<
17-
State, Action, DestinationState, DestinationAction, Destination: View
18-
>(
19-
store: Store<PresentationState<State>, PresentationAction<Action>>,
20-
state toDestinationState: @escaping (State) -> DestinationState?,
21-
action fromDestinationAction: @escaping (DestinationAction) -> Action,
22-
@ViewBuilder destination: @escaping (Store<DestinationState, DestinationAction>) ->
23-
Destination
24-
) -> some View {
25-
self.modifier(
26-
PresentationNavigationDestinationModifier(
27-
store: store,
28-
state: toDestinationState,
29-
action: fromDestinationAction,
30-
content: destination
31-
)
15+
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
16+
public func navigationDestination<
17+
State, Action, DestinationState, DestinationAction, Destination: View
18+
>(
19+
store: Store<PresentationState<State>, PresentationAction<Action>>,
20+
state toDestinationState: @escaping (State) -> DestinationState?,
21+
action fromDestinationAction: @escaping (DestinationAction) -> Action,
22+
@ViewBuilder destination: @escaping (Store<DestinationState, DestinationAction>) ->
23+
Destination
24+
) -> some View {
25+
self.modifier(
26+
PresentationNavigationDestinationModifier(
27+
store: store,
28+
state: toDestinationState,
29+
action: fromDestinationAction,
30+
content: destination
3231
)
33-
}
32+
)
3433
}
34+
}
3535

36-
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
37-
private struct PresentationNavigationDestinationModifier<
38-
State,
39-
Action,
40-
DestinationState,
41-
DestinationAction,
42-
DestinationContent: View
43-
>: ViewModifier {
44-
let store: Store<PresentationState<State>, PresentationAction<Action>>
45-
@StateObject var viewStore: ViewStore<Bool, PresentationAction<Action>>
46-
let toDestinationState: (State) -> DestinationState?
47-
let fromDestinationAction: (DestinationAction) -> Action
48-
let destinationContent: (Store<DestinationState, DestinationAction>) -> DestinationContent
36+
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
37+
private struct PresentationNavigationDestinationModifier<
38+
State,
39+
Action,
40+
DestinationState,
41+
DestinationAction,
42+
DestinationContent: View
43+
>: ViewModifier {
44+
let store: Store<PresentationState<State>, PresentationAction<Action>>
45+
@StateObject var viewStore: ViewStore<Bool, PresentationAction<Action>>
46+
let toDestinationState: (State) -> DestinationState?
47+
let fromDestinationAction: (DestinationAction) -> Action
48+
let destinationContent: (Store<DestinationState, DestinationAction>) -> DestinationContent
4949

50-
init(
51-
store: Store<PresentationState<State>, PresentationAction<Action>>,
52-
state toDestinationState: @escaping (State) -> DestinationState?,
53-
action fromDestinationAction: @escaping (DestinationAction) -> Action,
54-
content destinationContent:
55-
@escaping (Store<DestinationState, DestinationAction>) -> DestinationContent
56-
) {
57-
self.store = store
58-
self._viewStore = StateObject(
59-
wrappedValue: ViewStore(
60-
store
61-
.filterSend { state, _ in state.wrappedValue != nil }
62-
.scope(state: { $0.wrappedValue.flatMap(toDestinationState) != nil })
63-
)
50+
init(
51+
store: Store<PresentationState<State>, PresentationAction<Action>>,
52+
state toDestinationState: @escaping (State) -> DestinationState?,
53+
action fromDestinationAction: @escaping (DestinationAction) -> Action,
54+
content destinationContent:
55+
@escaping (Store<DestinationState, DestinationAction>) -> DestinationContent
56+
) {
57+
self.store = store
58+
self._viewStore = StateObject(
59+
wrappedValue: ViewStore(
60+
store
61+
.filterSend { state, _ in state.wrappedValue != nil }
62+
.scope(state: { $0.wrappedValue.flatMap(toDestinationState) != nil })
6463
)
65-
self.toDestinationState = toDestinationState
66-
self.fromDestinationAction = fromDestinationAction
67-
self.destinationContent = destinationContent
68-
}
64+
)
65+
self.toDestinationState = toDestinationState
66+
self.fromDestinationAction = fromDestinationAction
67+
self.destinationContent = destinationContent
68+
}
6969

70-
func body(content: Content) -> some View {
71-
content.navigationDestination(
72-
// TODO: do binding with ID check
73-
isPresented: self.viewStore.binding(send: .dismiss)
74-
) {
75-
IfLetStore(
76-
self.store.scope(
77-
state: returningLastNonNilValue { $0.wrappedValue.flatMap(self.toDestinationState) },
78-
action: { .presented(self.fromDestinationAction($0)) }
79-
),
80-
then: self.destinationContent
81-
)
82-
}
70+
func body(content: Content) -> some View {
71+
content.navigationDestination(
72+
// TODO: do binding with ID check
73+
unwrapping: self.viewStore.binding(send: .dismiss).presence
74+
) { _ in
75+
IfLetStore(
76+
self.store.scope(
77+
state: returningLastNonNilValue { $0.wrappedValue.flatMap(self.toDestinationState) },
78+
action: { .presented(self.fromDestinationAction($0)) }
79+
),
80+
then: self.destinationContent
81+
)
8382
}
8483
}
85-
#endif
84+
}
85+
86+
fileprivate extension Binding where Value == Bool {
87+
var presence: Binding<Void?> {
88+
.init(
89+
get: { self.wrappedValue ? () : nil },
90+
set: { self.transaction($1).wrappedValue = $0 != nil }
91+
)
92+
}
93+
}

0 commit comments

Comments
 (0)