Skip to content

Commit 16eaebb

Browse files
Merge pull request #114 from wwt/fix-the-things
Fixes some issues with preserving state while view swapping
2 parents 96b1989 + c840ecb commit 16eaebb

3 files changed

Lines changed: 63 additions & 11 deletions

File tree

Sources/SwiftCurrent_SwiftUI/Views/WorkflowItem.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public struct WorkflowItem<F: FlowRepresentable & View, Wrapped: View, Content:
3939
@State private var modifierClosure: ((AnyFlowRepresentableView) -> Void)?
4040
@State private var flowPersistenceClosure: (AnyWorkflow.PassedArgs) -> FlowPersistence = { _ in .default }
4141
@State private var launchStyle: LaunchStyle.SwiftUI.PresentationType = .default
42+
@State private var persistence: FlowPersistence = .default
4243

4344
@EnvironmentObject private var model: WorkflowViewModel
4445
@EnvironmentObject private var launcher: Launcher
@@ -47,21 +48,21 @@ public struct WorkflowItem<F: FlowRepresentable & View, Wrapped: View, Content:
4748

4849
public var body: some View {
4950
ViewBuilder {
50-
if model.isLaunched == true {
51-
if model.body?.extractErasedView() is Content {
52-
content
53-
} else {
54-
wrapped
55-
}
51+
if let body = model.body?.extractErasedView() as? Content {
52+
content ?? body
53+
} else {
54+
wrapped
5655
}
5756
}
5857
.onReceive(model.$body) {
5958
if let body = $0?.extractErasedView() as? Content {
6059
content = body
60+
persistence = $0?.value.metadata.persistence ?? .default
61+
} else if persistence == .removedAfterProceeding {
62+
content = nil
6163
}
6264
}
6365
.onReceive(inspection.notice) { inspection.visit(self, $0) }
64-
.onChange(of: model.isLaunched) { if $0 == false { resetWorkflow() } }
6566
}
6667

6768
private init<A, W, C, A1, W1, C1>(previous: WorkflowItem<A, W, C>, _ closure: () -> Wrapped) where Wrapped == WorkflowItem<A1, W1, C1> {
@@ -146,10 +147,6 @@ public struct WorkflowItem<F: FlowRepresentable & View, Wrapped: View, Content:
146147
flowPersistenceClosure: flowPersistenceClosure)
147148
}
148149

149-
private func resetWorkflow() {
150-
launcher.workflow.launch(withOrchestrationResponder: model, passedArgs: launcher.launchArgs)
151-
}
152-
153150
private func ViewBuilder<V: View>(@ViewBuilder builder: () -> V) -> some View { builder() }
154151

155152
private func factory(args: AnyWorkflow.PassedArgs) -> AnyFlowRepresentable {

Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,20 @@ public struct WorkflowLauncher<Content: View>: View {
4747
@StateObject private var launcher: Launcher
4848
@State private var onFinish = [(AnyWorkflow.PassedArgs) -> Void]()
4949
@State private var onAbandon = [() -> Void]()
50+
@Binding private var isLaunched: Bool
5051

5152
let inspection = Inspection<Self>()
5253

5354
public var body: some View {
55+
ViewBuilder {
56+
if isLaunched {
57+
workflowContent
58+
}
59+
}
60+
.onChange(of: isLaunched) { if $0 == false { resetWorkflow() } }
61+
}
62+
63+
private var workflowContent: some View {
5464
content
5565
.environmentObject(model)
5666
.environmentObject(launcher)
@@ -112,11 +122,13 @@ public struct WorkflowLauncher<Content: View>: View {
112122
_model = current._model
113123
_launcher = current._launcher
114124
_content = current._content
125+
_isLaunched = current._isLaunched
115126
_onFinish = State(initialValue: onFinish)
116127
_onAbandon = State(initialValue: onAbandon)
117128
}
118129

119130
private init<F, W, C>(isLaunched: Binding<Bool>, startingArgs: AnyWorkflow.PassedArgs, content: Content) where Content == WorkflowItem<F, W, C> {
131+
_isLaunched = isLaunched
120132
let wf = AnyWorkflow.empty
121133
content.modify(workflow: wf)
122134
let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: startingArgs)
@@ -127,6 +139,12 @@ public struct WorkflowLauncher<Content: View>: View {
127139
_content = State(wrappedValue: content)
128140
}
129141

142+
private func resetWorkflow() {
143+
launcher.workflow.launch(withOrchestrationResponder: model, passedArgs: launcher.launchArgs)
144+
}
145+
146+
private func ViewBuilder<V: View>(@ViewBuilder builder: () -> V) -> some View { builder() }
147+
130148
private func _onFinish(_ args: AnyWorkflow.PassedArgs?) {
131149
guard let args = args else { return }
132150
onFinish.forEach { $0(args) }

Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,43 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, View {
693693
XCTAssert($0.value is StateIdentifiable, "Property named: \(label) was note @State")
694694
}
695695
}
696+
697+
func testWorkflowCanHaveADelayedLaunch() throws {
698+
struct FR1: View, FlowRepresentable, Inspectable {
699+
weak var _workflowPointer: AnyFlowRepresentable?
700+
701+
var body: some View {
702+
Button("Proceed") { proceedInWorkflow() }
703+
}
704+
}
705+
706+
struct Wrapper: View, Inspectable {
707+
@State var showingWorkflow = false
708+
let inspection = Inspection<Self>()
709+
var body: some View {
710+
VStack {
711+
Button("") { showingWorkflow = true }
712+
WorkflowLauncher(isLaunched: $showingWorkflow) {
713+
thenProceed(with: FR1.self)
714+
}
715+
}
716+
.onReceive(inspection.notice) { inspection.visit(self, $0) }
717+
}
718+
}
719+
720+
let exp = ViewHosting.loadView(Wrapper()).inspection.inspect { view in
721+
let stack = try view.vStack()
722+
let launcher = try stack.view(WorkflowLauncher<WorkflowItem<FR1, Never, FR1>>.self, 1)
723+
XCTAssertThrowsError(try launcher.view(WorkflowItem<FR1, Never, FR1>.self))
724+
XCTAssertNoThrow(try stack.button(0).tap())
725+
let fr1 = try launcher.view(WorkflowItem<FR1, Never, FR1>.self)
726+
try fr1.actualView().inspect { fr1 in
727+
XCTAssertNoThrow(try fr1.find(FR1.self))
728+
}
729+
}
730+
731+
wait(for: [exp], timeout: TestConstant.timeout)
732+
}
696733
}
697734

698735
@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *)

0 commit comments

Comments
 (0)