Skip to content

Commit

Permalink
[die-anyview-die] - We have added compiler safety and painstakingly m…
Browse files Browse the repository at this point in the history
…ade sure we aren't lying to our users! - MOB
  • Loading branch information
Tyler-Keith-Thompson committed Jul 22, 2021
1 parent cc6c955 commit 1f459b1
Show file tree
Hide file tree
Showing 17 changed files with 115 additions and 137 deletions.
4 changes: 2 additions & 2 deletions ExampleApps/SwiftUIExample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ The project is designed to give you an idea of SwiftCurrent functionality while
Here is a list of things that are interesting to look at from a SwiftCurrent perspective.

### ContentView
This starting view shows the creation of the 3 main `WorkflowView`s and how SwiftCurrent can be helpful in a `TabView` setting.
This starting view shows the creation of the 3 main workflows and how SwiftCurrent can be helpful in a `TabView` setting.

### FeatureOnboardingViews
Each feature has an onboarding view. Look at these to see examples of single launch screens that can be placed at the start of a feature flow.

### AccountInformationView
This view launches a `WorkflowView` that contains a blocking view, `MFAView`, which is an example of SwiftCurrent usage for feature security.
This view launches a workflow that contains a blocking view, `MFAView`, which is an example of SwiftCurrent usage for feature security.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class ChangePasswordViewTests: XCTestCase {
func testChangePasswordProceeds_IfAllInformationIsCorrect() throws {
let currentPassword = UUID().uuidString
let onFinish = expectation(description: "onFinish called")
let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true), startingArgs: currentPassword)
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true), startingArgs: currentPassword)
.thenProceed(with: WorkflowItem(ChangePasswordView.self))
.onFinish { _ in onFinish.fulfill() }).inspection.inspect { view in
XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput(currentPassword))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ final class ContentViewTests: XCTestCase {
XCTAssertNotNil(wf2)
XCTAssertNotNil(wf3)
wait(for: [
ViewHosting.loadView(wf1)?.inspection.inspect { workflowView in
XCTAssertNoThrow(try workflowView.find(MapFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try workflowView.find(MapFeatureView.self))
ViewHosting.loadView(wf1)?.inspection.inspect { WorkflowLauncher in
XCTAssertNoThrow(try WorkflowLauncher.find(MapFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try WorkflowLauncher.find(MapFeatureView.self))
},
ViewHosting.loadView(wf2)?.inspection.inspect { workflowView in
XCTAssertNoThrow(try workflowView.find(QRScannerFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try workflowView.find(QRScannerFeatureView.self))
ViewHosting.loadView(wf2)?.inspection.inspect { WorkflowLauncher in
XCTAssertNoThrow(try WorkflowLauncher.find(QRScannerFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try WorkflowLauncher.find(QRScannerFeatureView.self))
},
ViewHosting.loadView(wf3)?.inspection.inspect { workflowView in
XCTAssertNoThrow(try workflowView.find(ProfileFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try workflowView.find(ProfileFeatureView.self))
ViewHosting.loadView(wf3)?.inspection.inspect { WorkflowLauncher in
XCTAssertNoThrow(try WorkflowLauncher.find(ProfileFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try WorkflowLauncher.find(ProfileFeatureView.self))
}
].compactMap { $0 }, timeout: TestConstant.timeout)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class MapFeatureOnboardingViewTests: XCTestCase {
defaults.set(false, forKey: defaultsKey)
Container.default.register(UserDefaults.self) { _ in defaults }
let workflowFinished = expectation(description: "View Proceeded")
let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true))
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(MapFeatureOnboardingView.self))
.onFinish { _ in
workflowFinished.fulfill()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class ProfileFeatureOnboardingViewTests: XCTestCase {
defaults.set(false, forKey: defaultsKey)
Container.default.register(UserDefaults.self) { _ in defaults }
let workflowFinished = expectation(description: "View Proceeded")
let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true))
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(ProfileFeatureOnboardingView.self))
.onFinish { _ in
workflowFinished.fulfill()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class QRScannerFeatureOnboardingViewTests: XCTestCase {
defaults.set(false, forKey: defaultsKey)
Container.default.register(UserDefaults.self) { _ in defaults }
let workflowFinished = expectation(description: "View Proceeded")
let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true))
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(QRScannerFeatureOnboardingView.self))
.onFinish { _ in
workflowFinished.fulfill()
Expand Down
6 changes: 3 additions & 3 deletions ExampleApps/SwiftUIExample/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ struct ContentView: View {
var body: some View {
TabView(selection: $selectedTab) {
// NOTE: Using constant here guarantees the workflow cannot abandon, it stays launched forever.
WorkflowView(isLaunched: .constant(true))
WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(MapFeatureOnboardingView.self))
.thenProceed(with: WorkflowItem(MapFeatureView.self))
.tabItem {
Label("Map", systemImage: "map")
}
.tag(Tab.map)

WorkflowView(isLaunched: .constant(true))
WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(QRScannerFeatureOnboardingView.self))
.thenProceed(with: WorkflowItem(QRScannerFeatureView.self))
.tabItem {
Label("QR Scanner", systemImage: "camera")
}
.tag(Tab.qr)

WorkflowView(isLaunched: .constant(true))
WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(ProfileFeatureOnboardingView.self))
.thenProceed(with: WorkflowItem(ProfileFeatureView.self))
.tabItem {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct AccountInformationView: View, FlowRepresentable {
}
}
} else {
WorkflowView(isLaunched: $usernameWorkflowLaunched, startingArgs: username)
WorkflowLauncher(isLaunched: $usernameWorkflowLaunched, startingArgs: username)
.thenProceed(with: WorkflowItem(MFAView.self))
.thenProceed(with: WorkflowItem(ChangeUsernameView.self))
.onFinish {
Expand All @@ -44,7 +44,7 @@ struct AccountInformationView: View, FlowRepresentable {
passwordWorkflowLaunched = true
}
} else {
WorkflowView(isLaunched: $passwordWorkflowLaunched, startingArgs: password)
WorkflowLauncher(isLaunched: $passwordWorkflowLaunched, startingArgs: password)
.thenProceed(with: WorkflowItem(MFAView.self))
.thenProceed(with: WorkflowItem(ChangePasswordView.self))
.onFinish {
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Then from your ContentView body, add:
```swift
import SwiftCurrent_SwiftUI
...
WorkflowView(isLaunched: .constant(true), startingArgs: "Launched")
WorkflowLauncher(isLaunched: .constant(true), startingArgs: "Launched")
.thenProceed(with: WorkflowItem(ExampleView.self))
```

Expand Down
58 changes: 29 additions & 29 deletions Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import SwiftUI
import SwiftCurrent

/**
A view used to store complex view type information from a `Workflow`
A view created by a `WorkflowLauncher`.

### Discussion
You do not instantiate this view directly, rather you call `thenProceed(with:)` on a `WorkflowView`.
You do not instantiate this view directly, rather you call `thenProceed(with:)` on a `WorkflowLauncher`.
*/
@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *)
public struct ModifiedWorkflowView<Args, Wrapped: View, Content: View>: View {
Expand Down Expand Up @@ -46,41 +46,41 @@ public struct ModifiedWorkflowView<Args, Wrapped: View, Content: View>: View {
}
}

init<A, FR>(_ workflowView: WorkflowView<A>, isLaunched: Binding<Bool>, item: WorkflowItem<FR, Content>) where Wrapped == Never, Args == FR.WorkflowOutput {
init<A, FR>(_ WorkflowLauncher: WorkflowLauncher<A>, isLaunched: Binding<Bool>, item: WorkflowItem<FR, Content>) where Wrapped == Never, Args == FR.WorkflowOutput {
wrapped = nil
let wf = AnyWorkflow(Workflow<FR>(item.metadata))
workflow = wf
launchArgs = workflowView.passedArgs
launchArgs = WorkflowLauncher.passedArgs
_isLaunched = isLaunched
onFinish = workflowView.onFinish
onAbandon = workflowView.onAbandon
onFinish = WorkflowLauncher.onFinish
onAbandon = WorkflowLauncher.onAbandon
let model = WorkflowViewModel(isLaunched: isLaunched)
_model = StateObject(wrappedValue: model)
_launcher = StateObject(wrappedValue: Launcher(workflow: wf,
responder: model,
launchArgs: workflowView.passedArgs))
launchArgs: WorkflowLauncher.passedArgs))
}

private init<A, W, C, FR>(_ workflowView: ModifiedWorkflowView<A, W, C>, item: WorkflowItem<FR, Content>) where Wrapped == ModifiedWorkflowView<A, W, C>, Args == FR.WorkflowOutput {
_model = workflowView._model
wrapped = workflowView
workflow = workflowView.workflow
private init<A, W, C, FR>(_ WorkflowLauncher: ModifiedWorkflowView<A, W, C>, item: WorkflowItem<FR, Content>) where Wrapped == ModifiedWorkflowView<A, W, C>, Args == FR.WorkflowOutput {
_model = WorkflowLauncher._model
wrapped = WorkflowLauncher
workflow = WorkflowLauncher.workflow
workflow.append(item.metadata)
launchArgs = workflowView.launchArgs
_isLaunched = workflowView._isLaunched
_launcher = workflowView._launcher
onAbandon = workflowView.onAbandon
launchArgs = WorkflowLauncher.launchArgs
_isLaunched = WorkflowLauncher._isLaunched
_launcher = WorkflowLauncher._launcher
onAbandon = WorkflowLauncher.onAbandon
}

private init(workflowView: Self, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) {
_model = workflowView._model
wrapped = workflowView.wrapped
workflow = workflowView.workflow
private init(WorkflowLauncher: Self, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) {
_model = WorkflowLauncher._model
wrapped = WorkflowLauncher.wrapped
workflow = WorkflowLauncher.workflow
self.onFinish = onFinish
self.onAbandon = onAbandon
launchArgs = workflowView.launchArgs
_isLaunched = workflowView._isLaunched
_launcher = workflowView._launcher
launchArgs = WorkflowLauncher.launchArgs
_isLaunched = WorkflowLauncher._isLaunched
_launcher = WorkflowLauncher._launcher
}

private func launch() {
Expand All @@ -97,14 +97,14 @@ public struct ModifiedWorkflowView<Args, Wrapped: View, Content: View>: View {
public func onFinish(closure: @escaping (AnyWorkflow.PassedArgs) -> Void) -> Self {
var onFinish = self.onFinish
onFinish.append(closure)
return Self(workflowView: self, onFinish: onFinish, onAbandon: onAbandon)
return Self(WorkflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon)
}

/// Adds an action to perform when this `Workflow` has abandoned.
public func onAbandon(closure: @escaping () -> Void) -> Self {
var onAbandon = self.onAbandon
onAbandon.append(closure)
return Self(workflowView: self, onFinish: onFinish, onAbandon: onAbandon)
return Self(WorkflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon)
}
}

Expand All @@ -125,7 +125,7 @@ extension ModifiedWorkflowView where Args == Never {
/**
Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward.
- Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow.
- Returns: a new `WorkflowView` with the additional `FlowRepresentable` item.
- Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item.
*/
public func thenProceed<FR: FlowRepresentable & View, T>(with item: WorkflowItem<FR, T>) -> ModifiedWorkflowView<FR.WorkflowOutput, Self, T> where FR.WorkflowInput == Never {
ModifiedWorkflowView<FR.WorkflowOutput, Self, T>(self, item: item)
Expand All @@ -137,7 +137,7 @@ extension ModifiedWorkflowView where Args == AnyWorkflow.PassedArgs {
/**
Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward.
- Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow.
- Returns: a new `WorkflowView` with the additional `FlowRepresentable` item.
- Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item.
*/
public func thenProceed<FR: FlowRepresentable & View, T>(with item: WorkflowItem<FR, T>) -> ModifiedWorkflowView<FR.WorkflowOutput, Self, T> where FR.WorkflowInput == AnyWorkflow.PassedArgs {
ModifiedWorkflowView<FR.WorkflowOutput, Self, T>(self, item: item)
Expand All @@ -146,7 +146,7 @@ extension ModifiedWorkflowView where Args == AnyWorkflow.PassedArgs {
/**
Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward.
- Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow.
- Returns: a new `WorkflowView` with the additional `FlowRepresentable` item.
- Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item.
*/
public func thenProceed<FR: FlowRepresentable & View, T>(with item: WorkflowItem<FR, T>) -> ModifiedWorkflowView<FR.WorkflowOutput, Self, T> {
ModifiedWorkflowView<FR.WorkflowOutput, Self, T>(self, item: item)
Expand All @@ -158,7 +158,7 @@ extension ModifiedWorkflowView {
/**
Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward.
- Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow.
- Returns: a new `WorkflowView` with the additional `FlowRepresentable` item.
- Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item.
*/
public func thenProceed<FR: FlowRepresentable & View, T>(with item: WorkflowItem<FR, T>) -> ModifiedWorkflowView<FR.WorkflowOutput, Self, T> where Args == FR.WorkflowInput {
ModifiedWorkflowView<FR.WorkflowOutput, Self, T>(self, item: item)
Expand All @@ -167,7 +167,7 @@ extension ModifiedWorkflowView {
/**
Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward.
- Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow.
- Returns: a new `WorkflowView` with the additional `FlowRepresentable` item.
- Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item.
*/
public func thenProceed<FR: FlowRepresentable & View, T>(with item: WorkflowItem<FR, T>) -> ModifiedWorkflowView<FR.WorkflowOutput, Self, T> where FR.WorkflowInput == AnyWorkflow.PassedArgs {
ModifiedWorkflowView<FR.WorkflowOutput, Self, T>(self, item: item)
Expand Down
Loading

0 comments on commit 1f459b1

Please sign in to comment.