Skip to content

Commit b0f041a

Browse files
Merge pull request #86 from wwt/relaunch-constant-onabandon
Fixes relaunching issue in SwiftUI when isLaunched is a constant of true
2 parents c092b4f + e317b68 commit b0f041a

File tree

4 files changed

+70
-29
lines changed

4 files changed

+70
-29
lines changed

Sources/SwiftCurrent_SwiftUI/Models/WorkflowViewModel.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ final class WorkflowViewModel: ObservableObject {
1717
let onFinishPublisher = CurrentValueSubject<AnyWorkflow.PassedArgs?, Never>(nil)
1818

1919
var isLaunched: Binding<Bool>?
20+
private let launchArgs: AnyWorkflow.PassedArgs
2021

21-
init(isLaunched: Binding<Bool>) {
22+
init(isLaunched: Binding<Bool>, launchArgs: AnyWorkflow.PassedArgs) {
2223
self.isLaunched = isLaunched
24+
self.launchArgs = launchArgs
2325
}
2426
}
2527

@@ -41,6 +43,9 @@ extension WorkflowViewModel: OrchestrationResponder {
4143
isLaunched?.wrappedValue = false
4244
body = nil
4345
onAbandonPublisher.send()
46+
if isLaunched?.wrappedValue == true {
47+
workflow.launch(withOrchestrationResponder: self, passedArgs: launchArgs)
48+
}
4449
}
4550

4651
func complete(_ workflow: AnyWorkflow, passedArgs: AnyWorkflow.PassedArgs, onFinish: ((AnyWorkflow.PassedArgs) -> Void)?) {

Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,41 +46,41 @@ public struct ModifiedWorkflowView<Args, Wrapped: View, Content: View>: View {
4646
}
4747
}
4848

49-
init<A, FR>(_ WorkflowLauncher: WorkflowLauncher<A>, isLaunched: Binding<Bool>, item: WorkflowItem<FR, Content>) where Wrapped == Never, Args == FR.WorkflowOutput {
49+
init<A, FR>(_ workflowLauncher: WorkflowLauncher<A>, isLaunched: Binding<Bool>, item: WorkflowItem<FR, Content>) where Wrapped == Never, Args == FR.WorkflowOutput {
5050
wrapped = nil
5151
let wf = AnyWorkflow(Workflow<FR>(item.metadata))
5252
workflow = wf
53-
launchArgs = WorkflowLauncher.passedArgs
53+
launchArgs = workflowLauncher.passedArgs
5454
_isLaunched = isLaunched
55-
onFinish = WorkflowLauncher.onFinish
56-
onAbandon = WorkflowLauncher.onAbandon
57-
let model = WorkflowViewModel(isLaunched: isLaunched)
55+
onFinish = workflowLauncher.onFinish
56+
onAbandon = workflowLauncher.onAbandon
57+
let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: workflowLauncher.passedArgs)
5858
_model = StateObject(wrappedValue: model)
5959
_launcher = StateObject(wrappedValue: Launcher(workflow: wf,
6060
responder: model,
61-
launchArgs: WorkflowLauncher.passedArgs))
61+
launchArgs: workflowLauncher.passedArgs))
6262
}
6363

64-
private init<A, W, C, FR>(_ WorkflowLauncher: ModifiedWorkflowView<A, W, C>, item: WorkflowItem<FR, Content>) where Wrapped == ModifiedWorkflowView<A, W, C>, Args == FR.WorkflowOutput {
65-
_model = WorkflowLauncher._model
66-
wrapped = WorkflowLauncher
67-
workflow = WorkflowLauncher.workflow
64+
private init<A, W, C, FR>(_ workflowLauncher: ModifiedWorkflowView<A, W, C>, item: WorkflowItem<FR, Content>) where Wrapped == ModifiedWorkflowView<A, W, C>, Args == FR.WorkflowOutput {
65+
_model = workflowLauncher._model
66+
wrapped = workflowLauncher
67+
workflow = workflowLauncher.workflow
6868
workflow.append(item.metadata)
69-
launchArgs = WorkflowLauncher.launchArgs
70-
_isLaunched = WorkflowLauncher._isLaunched
71-
_launcher = WorkflowLauncher._launcher
72-
onAbandon = WorkflowLauncher.onAbandon
69+
launchArgs = workflowLauncher.launchArgs
70+
_isLaunched = workflowLauncher._isLaunched
71+
_launcher = workflowLauncher._launcher
72+
onAbandon = workflowLauncher.onAbandon
7373
}
7474

75-
private init(WorkflowLauncher: Self, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) {
76-
_model = WorkflowLauncher._model
77-
wrapped = WorkflowLauncher.wrapped
78-
workflow = WorkflowLauncher.workflow
75+
private init(workflowLauncher: Self, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) {
76+
_model = workflowLauncher._model
77+
wrapped = workflowLauncher.wrapped
78+
workflow = workflowLauncher.workflow
7979
self.onFinish = onFinish
8080
self.onAbandon = onAbandon
81-
launchArgs = WorkflowLauncher.launchArgs
82-
_isLaunched = WorkflowLauncher._isLaunched
83-
_launcher = WorkflowLauncher._launcher
81+
launchArgs = workflowLauncher.launchArgs
82+
_isLaunched = workflowLauncher._isLaunched
83+
_launcher = workflowLauncher._launcher
8484
}
8585

8686
private func launch() {
@@ -97,14 +97,14 @@ public struct ModifiedWorkflowView<Args, Wrapped: View, Content: View>: View {
9797
public func onFinish(closure: @escaping (AnyWorkflow.PassedArgs) -> Void) -> Self {
9898
var onFinish = self.onFinish
9999
onFinish.append(closure)
100-
return Self(WorkflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon)
100+
return Self(workflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon)
101101
}
102102

103103
/// Adds an action to perform when this `Workflow` has abandoned.
104104
public func onAbandon(closure: @escaping () -> Void) -> Self {
105105
var onAbandon = self.onAbandon
106106
onAbandon.append(closure)
107-
return Self(WorkflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon)
107+
return Self(workflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon)
108108
}
109109
}
110110

Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,39 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase {
485485
wait(for: [expectViewLoaded], timeout: TestConstant.timeout)
486486
}
487487

488+
func testWorkflowRelaunchesWhenAbandoned_WithAConstantOfTrue() throws {
489+
struct FR1: View, FlowRepresentable, Inspectable {
490+
var _workflowPointer: AnyFlowRepresentable?
491+
var body: some View { Text("FR1 type") }
492+
}
493+
struct FR2: View, FlowRepresentable, Inspectable {
494+
var _workflowPointer: AnyFlowRepresentable?
495+
var body: some View { Text("FR2 type") }
496+
497+
func abandon() {
498+
workflow?.abandon()
499+
}
500+
}
501+
let onFinishCalled = expectation(description: "onFinish Called")
502+
503+
let workflowView = WorkflowLauncher(isLaunched: .constant(true))
504+
.thenProceed(with: WorkflowItem(FR1.self))
505+
.thenProceed(with: WorkflowItem(FR2.self))
506+
.onFinish { _ in
507+
onFinishCalled.fulfill()
508+
}
509+
let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in
510+
XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow())
511+
XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().abandon())
512+
513+
XCTAssertThrowsError(try viewUnderTest.find(FR2.self))
514+
XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow())
515+
XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow())
516+
}
517+
518+
wait(for: [expectViewLoaded, onFinishCalled], timeout: TestConstant.timeout)
519+
}
520+
488521
func testWorkflowCanHaveAPassthroughRepresentable() throws {
489522
struct FR1: View, FlowRepresentable, Inspectable {
490523
typealias WorkflowOutput = AnyWorkflow.PassedArgs

Tests/SwiftCurrent_SwiftUITests/WorkflowViewModelTests.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,37 @@
77
//
88

99
import XCTest
10+
import ViewInspector
11+
import SwiftUI
1012

1113
@testable import SwiftCurrent
1214
@testable import SwiftCurrent_SwiftUI
1315

1416
@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *)
1517
final class WorkflowViewModelTests: XCTestCase {
1618
func testWorkflowViewModelThrowsFatalError_WhenLaunchedWithSomethingOtherThan_AnyFlowRepresentableView() {
17-
let model = WorkflowViewModel(isLaunched: .constant(true))
19+
let model = WorkflowViewModel(isLaunched: .constant(true), launchArgs: .none)
1820
XCTAssertThrowsFatalError {
1921
model.launch(to: .createForTests(FR.self))
2022
}
2123
}
2224

2325
func testWorkflowViewModelThrowsFatalError_WhenProceedingWithSomethingOtherThan_AnyFlowRepresentableView() {
24-
let model = WorkflowViewModel(isLaunched: .constant(true))
26+
let model = WorkflowViewModel(isLaunched: .constant(true), launchArgs: .none)
2527
XCTAssertThrowsFatalError {
2628
model.proceed(to: .createForTests(FR.self), from: .createForTests(FR.self))
2729
}
2830
}
2931

3032
func testWorkflowViewModelThrowsFatalError_WhenBackingUpWithSomethingOtherThan_AnyFlowRepresentableView() {
31-
let model = WorkflowViewModel(isLaunched: .constant(true))
33+
let model = WorkflowViewModel(isLaunched: .constant(true), launchArgs: .none)
3234
XCTAssertThrowsFatalError {
3335
model.backUp(from: .createForTests(FR.self), to: .createForTests(FR.self))
3436
}
3537
}
3638

3739
func testWorkflowViewModelThrowsFatalError_WhenCompletingWithSomethingOtherThan_AnyFlowRepresentableView() {
38-
let model = WorkflowViewModel(isLaunched: .constant(true))
40+
let model = WorkflowViewModel(isLaunched: .constant(true), launchArgs: .none)
3941
let typedWorkflow = Workflow(FR.self).thenProceed(with: FR.self, flowPersistence: .removedAfterProceeding)
4042
let mock = MockOrchestrationResponder()
4143
let firstLoadedInstance = typedWorkflow.launch(withOrchestrationResponder: mock)
@@ -46,7 +48,8 @@ final class WorkflowViewModelTests: XCTestCase {
4648
}
4749

4850
func testWorkflowViewModelSetsBodyToNilWhenAbandoning() {
49-
let model = WorkflowViewModel(isLaunched: .constant(true))
51+
let isLaunched = Binding(wrappedValue: true)
52+
let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: .none)
5053
model.body = ""
5154
let typedWorkflow = Workflow(FR.self)
5255
model.abandon(AnyWorkflow(typedWorkflow), onFinish: nil)

0 commit comments

Comments
 (0)