Skip to content

Commit

Permalink
Merge pull request #114 from wwt/fix-the-things
Browse files Browse the repository at this point in the history
Fixes some issues with preserving state while view swapping
  • Loading branch information
morganzellers committed Aug 27, 2021
2 parents 96b1989 + c840ecb commit 16eaebb
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 11 deletions.
19 changes: 8 additions & 11 deletions Sources/SwiftCurrent_SwiftUI/Views/WorkflowItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public struct WorkflowItem<F: FlowRepresentable & View, Wrapped: View, Content:
@State private var modifierClosure: ((AnyFlowRepresentableView) -> Void)?
@State private var flowPersistenceClosure: (AnyWorkflow.PassedArgs) -> FlowPersistence = { _ in .default }
@State private var launchStyle: LaunchStyle.SwiftUI.PresentationType = .default
@State private var persistence: FlowPersistence = .default

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

public var body: some View {
ViewBuilder {
if model.isLaunched == true {
if model.body?.extractErasedView() is Content {
content
} else {
wrapped
}
if let body = model.body?.extractErasedView() as? Content {
content ?? body
} else {
wrapped
}
}
.onReceive(model.$body) {
if let body = $0?.extractErasedView() as? Content {
content = body
persistence = $0?.value.metadata.persistence ?? .default
} else if persistence == .removedAfterProceeding {
content = nil
}
}
.onReceive(inspection.notice) { inspection.visit(self, $0) }
.onChange(of: model.isLaunched) { if $0 == false { resetWorkflow() } }
}

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

private func resetWorkflow() {
launcher.workflow.launch(withOrchestrationResponder: model, passedArgs: launcher.launchArgs)
}

private func ViewBuilder<V: View>(@ViewBuilder builder: () -> V) -> some View { builder() }

private func factory(args: AnyWorkflow.PassedArgs) -> AnyFlowRepresentable {
Expand Down
18 changes: 18 additions & 0 deletions Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,20 @@ public struct WorkflowLauncher<Content: View>: View {
@StateObject private var launcher: Launcher
@State private var onFinish = [(AnyWorkflow.PassedArgs) -> Void]()
@State private var onAbandon = [() -> Void]()
@Binding private var isLaunched: Bool

let inspection = Inspection<Self>()

public var body: some View {
ViewBuilder {
if isLaunched {
workflowContent
}
}
.onChange(of: isLaunched) { if $0 == false { resetWorkflow() } }
}

private var workflowContent: some View {
content
.environmentObject(model)
.environmentObject(launcher)
Expand Down Expand Up @@ -112,11 +122,13 @@ public struct WorkflowLauncher<Content: View>: View {
_model = current._model
_launcher = current._launcher
_content = current._content
_isLaunched = current._isLaunched
_onFinish = State(initialValue: onFinish)
_onAbandon = State(initialValue: onAbandon)
}

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

private func resetWorkflow() {
launcher.workflow.launch(withOrchestrationResponder: model, passedArgs: launcher.launchArgs)
}

private func ViewBuilder<V: View>(@ViewBuilder builder: () -> V) -> some View { builder() }

private func _onFinish(_ args: AnyWorkflow.PassedArgs?) {
guard let args = args else { return }
onFinish.forEach { $0(args) }
Expand Down
37 changes: 37 additions & 0 deletions Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,43 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, View {
XCTAssert($0.value is StateIdentifiable, "Property named: \(label) was note @State")
}
}

func testWorkflowCanHaveADelayedLaunch() throws {
struct FR1: View, FlowRepresentable, Inspectable {
weak var _workflowPointer: AnyFlowRepresentable?

var body: some View {
Button("Proceed") { proceedInWorkflow() }
}
}

struct Wrapper: View, Inspectable {
@State var showingWorkflow = false
let inspection = Inspection<Self>()
var body: some View {
VStack {
Button("") { showingWorkflow = true }
WorkflowLauncher(isLaunched: $showingWorkflow) {
thenProceed(with: FR1.self)
}
}
.onReceive(inspection.notice) { inspection.visit(self, $0) }
}
}

let exp = ViewHosting.loadView(Wrapper()).inspection.inspect { view in
let stack = try view.vStack()
let launcher = try stack.view(WorkflowLauncher<WorkflowItem<FR1, Never, FR1>>.self, 1)
XCTAssertThrowsError(try launcher.view(WorkflowItem<FR1, Never, FR1>.self))
XCTAssertNoThrow(try stack.button(0).tap())
let fr1 = try launcher.view(WorkflowItem<FR1, Never, FR1>.self)
try fr1.actualView().inspect { fr1 in
XCTAssertNoThrow(try fr1.find(FR1.self))
}
}

wait(for: [exp], timeout: TestConstant.timeout)
}
}

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

0 comments on commit 16eaebb

Please sign in to comment.