Skip to content

Commit 16b0dac

Browse files
Merge pull request #211 from wwt/Issue-96
Add the ability to abandon workflow when a publisher emits a value
2 parents 16c0142 + aefb179 commit 16b0dac

3 files changed

Lines changed: 53 additions & 4 deletions

File tree

Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// Copyright © 2021 WWT and Tyler Thompson. All rights reserved.
77
//
88

9+
import Combine
910
import SwiftUI
1011
import SwiftCurrent
1112

@@ -15,6 +16,7 @@ public struct WorkflowLauncher<Content: _WorkflowItemProtocol>: View {
1516
public typealias WorkflowInput = Content.FlowRepresentableType.WorkflowInput
1617

1718
@WorkflowBuilder private var content: Content
19+
@State private var abandonOnPublisher: AnyPublisher<Void, Never> = Empty(completeImmediately: false).eraseToAnyPublisher()
1820
@State private var onFinish = [(AnyWorkflow.PassedArgs) -> Void]()
1921
@State private var onAbandon = [() -> Void]()
2022
@State private var shouldEmbedInNavView = false
@@ -38,6 +40,7 @@ public struct WorkflowLauncher<Content: _WorkflowItemProtocol>: View {
3840
}
3941
}
4042
.onChange(of: isLaunched) { if $0 == false { resetWorkflow() } }
43+
.onReceive(abandonOnPublisher) { launcher.workflow.abandon() }
4144
}
4245

4346
private var workflowContent: some View {
@@ -61,14 +64,19 @@ public struct WorkflowLauncher<Content: _WorkflowItemProtocol>: View {
6164
self.content = content()
6265
}
6366

64-
private init(current: Self, shouldEmbedInNavView: Bool, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) {
67+
private init(current: Self,
68+
abandonOnPublisher: AnyPublisher<Void, Never> = Empty(completeImmediately: false).eraseToAnyPublisher(),
69+
shouldEmbedInNavView: Bool,
70+
onFinish: [(AnyWorkflow.PassedArgs) -> Void],
71+
onAbandon: [() -> Void]) {
6572
_model = current._model
6673
_launcher = current._launcher
6774
content = current.content
6875
_isLaunched = current._isLaunched
69-
_shouldEmbedInNavView = State(initialValue: shouldEmbedInNavView)
70-
_onFinish = State(initialValue: onFinish)
71-
_onAbandon = State(initialValue: onAbandon)
76+
_shouldEmbedInNavView = State(wrappedValue: shouldEmbedInNavView)
77+
_onFinish = State(wrappedValue: onFinish)
78+
_onAbandon = State(wrappedValue: onAbandon)
79+
_abandonOnPublisher = State(wrappedValue: abandonOnPublisher)
7280
}
7381

7482
private func resetWorkflow() {
@@ -92,6 +100,14 @@ public struct WorkflowLauncher<Content: _WorkflowItemProtocol>: View {
92100
return Self(current: self, shouldEmbedInNavView: shouldEmbedInNavView, onFinish: onFinish, onAbandon: onAbandon)
93101
}
94102

103+
func abandonOn<P: Publisher>(_ publisher: P) -> Self where P.Failure == Never {
104+
Self(current: self,
105+
abandonOnPublisher: publisher.map { _ in () }.eraseToAnyPublisher(),
106+
shouldEmbedInNavView: shouldEmbedInNavView,
107+
onFinish: onFinish,
108+
onAbandon: onAbandon)
109+
}
110+
95111
func embedInNavigationView() -> Self {
96112
Self(current: self, shouldEmbedInNavView: true, onFinish: onFinish, onAbandon: onAbandon)
97113
}

Sources/SwiftCurrent_SwiftUI/Views/WorkflowView.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// Copyright © 2022 WWT and Tyler Thompson. All rights reserved.
77
//
88

9+
import Combine
910
import SwiftUI
1011
import SwiftCurrent
1112

@@ -202,6 +203,11 @@ public struct WorkflowView<Content: View>: View {
202203
Self(self, newContent: _content.wrappedValue.onAbandon(closure: closure))
203204
}
204205

206+
/// Subscribers to a combine publisher, when a value is emitted the workflow will abandon.
207+
public func abandonOn<WI: _WorkflowItemProtocol, P: Publisher>(_ publisher: P) -> Self where Content == WorkflowLauncher<WI>, P.Failure == Never {
208+
Self(self, newContent: _content.wrappedValue.abandonOn(publisher))
209+
}
210+
205211
/// Wraps content in a NavigationView.
206212
public func embedInNavigationView<WI: _WorkflowItemProtocol>() -> Self where Content == WorkflowLauncher<WI> {
207213
Self(self, newContent: _content.wrappedValue.embedInNavigationView())

Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import XCTest
1010
import SwiftUI
11+
import Combine
1112
import ViewInspector
1213

1314
import SwiftCurrent
@@ -529,6 +530,32 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase {
529530
wait(for: [expectOnAbandon1, expectOnAbandon2], timeout: TestConstant.timeout)
530531
}
531532

533+
func testWorkflowCanAbandonFromAPublisher() async throws {
534+
let publisher = PassthroughSubject<Void, Never>()
535+
struct FR1: View, FlowRepresentable, Inspectable {
536+
var _workflowPointer: AnyFlowRepresentable?
537+
var body: some View { Text("FR1 type") }
538+
}
539+
let isLaunched = Binding(wrappedValue: true)
540+
let expectOnAbandon = expectation(description: "OnAbandon called")
541+
let launcher = try await MainActor.run {
542+
WorkflowView(isLaunched: isLaunched) {
543+
WorkflowItem(FR1.self)
544+
}
545+
.onAbandon {
546+
XCTAssertFalse(isLaunched.wrappedValue)
547+
expectOnAbandon.fulfill()
548+
}
549+
.abandonOn(publisher)
550+
}.hostAndInspect(with: \.inspection)
551+
552+
XCTAssertEqual(try launcher.find(FR1.self).text().string(), "FR1 type")
553+
publisher.send()
554+
XCTAssertThrowsError(try launcher.find(FR1.self))
555+
556+
wait(for: [expectOnAbandon], timeout: TestConstant.timeout)
557+
}
558+
532559
func testWorkflowCanHaveModifiers() async throws {
533560
struct FR1: View, FlowRepresentable, Inspectable {
534561
var _workflowPointer: AnyFlowRepresentable?

0 commit comments

Comments
 (0)