Skip to content

Commit 4f1da41

Browse files
committed
Update VDFlow dependency to version 4.32.0 and enhance documentation for StateStep with detailed usage examples and new initializers for state management.
1 parent 450b399 commit 4f1da41

File tree

2 files changed

+144
-4
lines changed

2 files changed

+144
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ import PackageDescription
243243
let package = Package(
244244
name: "SomeProject",
245245
dependencies: [
246-
.package(url: "https://github.com/dankinsoid/VDFlow.git", from: "4.31.0")
246+
.package(url: "https://github.com/dankinsoid/VDFlow.git", from: "4.32.0")
247247
],
248248
targets: [
249249
.target(name: "SomeProject", dependencies: ["VDFlow"])

Sources/VDFlow/StateStep.swift

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,64 @@
11
import SwiftUI
22

3-
/// A property wrapper type that stores or binds a ``VDFlow/Step`` and invalidates a view whenever the step changes.
3+
/// A property wrapper type that stores or binds a navigation step collection and invalidates a view whenever the step changes.
44
///
5-
/// @StateStep var router = StepsStruct()
6-
/// let stepState = StateStep(stepBinding)
5+
/// `StateStep` serves as the primary way to integrate VDFlow's navigation with SwiftUI's view system.
6+
/// It can either own the state itself (like `@State`) or bind to external state (like `@Binding`).
7+
///
8+
/// There are three primary ways to use `StateStep`:
9+
///
10+
/// 1. Create and own the state:
11+
/// ```swift
12+
/// struct ContentView: View {
13+
/// @StateStep var steps = TabSteps.home
14+
///
15+
/// var body: some View {
16+
/// TabView(selection: $steps.selected) {
17+
/// HomeView()
18+
/// .step(_steps.$home)
19+
/// ProfileView()
20+
/// .step(_steps.$profile)
21+
/// }
22+
/// }
23+
/// }
24+
/// ```
25+
///
26+
/// 2. Bind to a parent's state:
27+
/// ```swift
28+
/// struct ChildView: View {
29+
/// @StateStep var steps: TabSteps
30+
///
31+
/// var body: some View {
32+
/// // Access and modify steps here
33+
/// Button("Go Home") {
34+
/// steps.selected = .home
35+
/// }
36+
/// }
37+
/// }
38+
///
39+
/// // In parent view:
40+
/// ChildView()
41+
/// .stepEnvironment($parentSteps)
42+
/// ```
43+
///
44+
/// 3. Create from an existing binding:
45+
/// ```swift
46+
/// struct DetailView: View {
47+
/// @ObservedObject var viewModel: ViewModel // Has a steps property
48+
/// @StateStep var steps: DetailSteps
49+
///
50+
/// init(viewModel: ViewModel) {
51+
/// self.viewModel = viewModel
52+
/// _steps = StateStep(viewModel.$steps)
53+
/// }
54+
///
55+
/// var body: some View {
56+
/// // Use steps here
57+
/// }
58+
/// }
59+
/// ```
60+
///
61+
/// - Important: When accessing the wrapper itself, use the underscore prefix: `_steps`
762
@propertyWrapper
863
public struct StateStep<Value>: DynamicProperty {
964

@@ -25,10 +80,21 @@ public struct StateStep<Value>: DynamicProperty {
2580
}
2681
}
2782

83+
/// Creates a new state step that owns its state.
84+
///
85+
/// This initializer creates a state step that owns and manages its own state, similar to SwiftUI's `@State`.
86+
///
87+
/// - Parameter wrappedValue: The initial value for the state.
2888
public init(wrappedValue: Value) {
2989
self.init(binding: .state(wrappedValue))
3090
}
3191

92+
/// Creates a new state step that binds to external state.
93+
///
94+
/// This initializer creates a state step that binds to external state, similar to SwiftUI's `@Binding`.
95+
/// Use this when you want to integrate with existing state management like `ObservableObject` models.
96+
///
97+
/// - Parameter binding: A binding to the external state.
3298
public init(_ binding: Binding<Value>) {
3399
self.init(binding: .binding(binding))
34100
}
@@ -45,6 +111,9 @@ public struct StateStep<Value>: DynamicProperty {
45111

46112
public extension StateStep where Value == EmptyStep {
47113

114+
/// Creates a new state step with an empty step value.
115+
///
116+
/// This is a convenience initializer for cases where you need a placeholder step that doesn't contain any data.
48117
init() {
49118
self.init(wrappedValue: EmptyStep())
50119
}
@@ -60,6 +129,23 @@ extension EnvironmentValues {
60129

61130
public extension View {
62131

132+
/// Associates a view with a specific step in a step collection and sets up the environment for that step.
133+
///
134+
/// This modifier combines `.tag()` and `.stepEnvironment()` to fully configure a view for a specific step.
135+
/// Use this with tab views, lists, or any other view where selection state is needed.
136+
///
137+
/// - Parameter binding: A binding to a step wrapper, typically accessed through `_steps.$stepName`.
138+
/// - Returns: A view modified with the appropriate tag and environment for this step.
139+
///
140+
/// ```swift
141+
/// TabView(selection: $steps.selected) {
142+
/// HomeView()
143+
/// .step(_steps.$home)
144+
///
145+
/// ProfileView()
146+
/// .step(_steps.$profile)
147+
/// }
148+
/// ```
63149
func step<Root: StepsCollection, Value>(
64150
_ binding: Binding<StepWrapper<Root, Value>>
65151
) -> some View {
@@ -70,13 +156,48 @@ public extension View {
70156
.stepTag(binding.wrappedValue.id)
71157
}
72158

159+
/// Sets up the environment to provide a binding to the specified value to child views.
160+
///
161+
/// This is particularly useful for passing a step binding down the view hierarchy without
162+
/// needing to explicitly pass it through initializers.
163+
///
164+
/// - Parameter binding: A binding to a value that will be made available to child views.
165+
/// - Returns: A view with the environment set up for the provided binding.
166+
///
167+
/// ```swift
168+
/// NavigationView {
169+
/// RootView()
170+
/// .stepEnvironment($steps.$detailFlow)
171+
/// }
172+
///
173+
/// // In a child view:
174+
/// struct DetailView: View {
175+
/// @StateStep var detailFlow: DetailSteps
176+
/// // This will automatically receive the binding from the environment
177+
/// }
178+
/// ```
73179
func stepEnvironment<Value>(_ binding: Binding<Value>) -> some View {
74180
environment(\.[StateStep<Value>.StepKey()], binding)
75181
}
76182
}
77183

78184
public extension Binding where Value: StepsCollection, Value.AllSteps: ExpressibleByNilLiteral {
79185

186+
/// Creates a binding to an optional value that reflects whether a specific step is selected.
187+
///
188+
/// This is useful for integrating with SwiftUI's `isPresented` and similar APIs that use optional bindings.
189+
/// The binding's value will be non-nil when the step is selected, and nil otherwise.
190+
///
191+
/// - Parameter step: The key path to the step to check.
192+
/// - Returns: A binding to an optional value that is non-nil when the step is selected.
193+
///
194+
/// ```swift
195+
/// NavigationLink(isActive: $steps.isSelected(\.detail)) {
196+
/// DetailView()
197+
/// } label: {
198+
/// Text("Go to Detail")
199+
/// }
200+
/// ```
80201
func isSelected<T>(_ step: WritableKeyPath<Value, Value.StepID<T>>) -> Binding<T?> {
81202
Binding<T?> {
82203
if wrappedValue.isSelected(step) {
@@ -93,6 +214,25 @@ public extension Binding where Value: StepsCollection, Value.AllSteps: Expressib
93214
}
94215
}
95216

217+
/// Creates a binding to a Boolean value that reflects whether a specific step is selected.
218+
///
219+
/// This is useful for integrating with SwiftUI's `isPresented` and similar APIs that use Boolean bindings.
220+
///
221+
/// - Parameter step: The step to check.
222+
/// - Returns: A binding to a Boolean value that is `true` when the step is selected.
223+
///
224+
/// ```swift
225+
/// Button("Go to Settings") {
226+
/// $flow.isSelected(.settings).wrappedValue = true
227+
/// }
228+
///
229+
/// // Or with NavigationLink:
230+
/// NavigationLink(isActive: $flow.isSelected(.details)) {
231+
/// DetailsView()
232+
/// } label: {
233+
/// Text("Show Details")
234+
/// }
235+
/// ```
96236
func isSelected(_ step: Value.AllSteps) -> Binding<Bool> {
97237
Binding<Bool> {
98238
wrappedValue.selected == step

0 commit comments

Comments
 (0)