Coder Social home page Coder Social logo

wwt / swiftcurrent Goto Github PK

View Code? Open in Web Editor NEW
306.0 16.0 19.0 13.42 MB

A library for managing complex workflows in Swift

Home Page: https://wwt.github.io/SwiftCurrent/

License: Apache License 2.0

Ruby 0.20% Swift 99.60% Objective-C 0.20%
swift ios uikit workflows workflow swift-package-manager cocoapods swiftui watchos tvos

swiftcurrent's Introduction

SwiftCurrent

Supported Platforms Swift Package Manager Pod Version License Build Status Code Coverage

Welcome

SwiftCurrent is a library that lets you easily manage journeys through your Swift application and comes with built-in support for UIKit and SwiftUI app-routing.

Why Should I Use SwiftCurrent?

In SwiftCurrent, workflows are a sequence of operations. Those operations usually display views in an application. The workflow describes the sequence of views and manages which view should come next. Your views are responsible for performing necessary tasks before proceeding forward in the workflow, like processing user input.

Architectural patterns and libraries that attempt to create a separation between views and workflows already exist. However, SwiftCurrent is different. We took a new design approach that focuses on:

  • A Developer-Friendly API. The library was built with developers in mind. It started with a group of developers talking about the code experience they desired. Then the library team took on whatever complexities were necessary to bring them that experience.
  • Compile-Time Safety. At compile-time, we tell you everything we can so you know things will work.
  • Minimal Boilerplate. We have hidden this as much as possible. We hate it as much as you do and are constantly working on cutting the cruft.

From There, We Created a Library

This library:

  • Isolates Your Views. Design your views so that they are unaware of the view that will come next.
  • Easily Reorders Views. Changing view order is as easy as โŒ˜+โŒฅ+[ (moving the line up or down).
  • Composes Workflows Together. Create branching flows easily by joining workflows together.
  • Creates Conditional Flows. Make your flows robust and handle ever-changing designs. Need a screen to only to show up sometimes? Need a flow for person A and another for person B? We've got you covered.

Quick Start

Why show a quick start when we have an example app? Because it's so easy to get started, we can drop in two code snippets, and you're ready to go! This quick start uses Swift Package Manager and SwiftUI, but for other approaches, see our installation instructions.

.package(url: "https://github.com/wwt/SwiftCurrent.git", .upToNextMajor(from: "5.1.0")),
...
.product(name: "SwiftCurrent", package: "SwiftCurrent"),
.product(name: "SwiftCurrent_SwiftUI", package: "SwiftCurrent")

Then make your first FlowRepresentable view:

import SwiftCurrent
import SwiftUI
struct OptionalView: View, FlowRepresentable {
    weak var _workflowPointer: AnyFlowRepresentable?
    let input: String
    init(with args: String) { input = args }
    var body: some View { Text("Only shows up if no input") }
    func shouldLoad() -> Bool { input.isEmpty }
}
struct ExampleView: View, PassthroughFlowRepresentable {
    weak var _workflowPointer: AnyFlowRepresentable?
    var body: some View { Text("This is ExampleView!") }
}

Then from your ContentView or whatever view (or app) you'd like to contain the workflow, add the following view to the body:

import SwiftCurrent_SwiftUI
// ...
var body: some View { 
    // ... other view code (if any)
    WorkflowView(launchingWith: "Skip optional screen") {
        WorkflowItem(OptionalView.self)
        WorkflowItem(ExampleView.self)
    }
}

And just like that, you've got a workflow! You can now add more items to it or reorder the items that are there. To understand more of how this works, check out our developer docs.

Server Driven Workflows

SwiftCurrent now supports server driven workflows! Check out our schema for details on defining workflows with JSON, YAML, or any other key/value-based data format. Then, simply have your FlowRepresentable types that you wish to decode conform to WorkflowDecodable and decode the workflow. For more information, see our docs.

Look at Our Example Apps

We have example apps for both SwiftUI and UIKit that show SwiftCurrent in action. They've already been tested, so you can see what it's like to test SwiftCurrent code. To run it locally, start by cloning the repo, open SwiftCurrent.xcworkspace and then run the SwiftUIExample scheme or the UIKitExample scheme.

For specific documentation check out:

Feedback

If you like what you've seen, consider giving us a star! If you don't, let us know how we can improve.

Stars

Special Thanks

SwiftCurrent would not be nearly as amazing without all of the great work done by the authors of our test dependencies:

swiftcurrent's People

Contributors

brianlombardo avatar courtneybeisner avatar hmeadow avatar mattfreiburgasynchrony avatar morganzellers avatar nick-nallick-wwt avatar nickkaczmarek avatar pg avatar richard-gist avatar tyler-keith-thompson avatar wiemerm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swiftcurrent's Issues

Relaunching a workflow using onChange

Describe the bug

If you use the onChange modifier of a view to listen to when a workflow is no longer launched, then relaunch that way instead of with a .constant(true), the workflow just vanishes.

To Reproduce

Steps to reproduce the behavior: ALL CHANGES TO CONTENTVIEW

  1. In our sample app, modify the profile workflow to have a @State variable that binds it instead of .constant(true)
  2. Add anonChange(of: profileWorkflowShown) { if !$0 { profileWorkflowShown = true } } modified to the TabView, or wherever, it doesn't matter.
  3. Notice that profile simply vanishes and never really re-appears.

Expected behavior

The workflow should relaunch, just as if you were using .constant(true)

Screenshots

You want a screenshot of nothing? Heh, no

Better Test Experience

Is your feature request related to a problem? Please describe.

I'm frustrated by the current test experience Workflow provides.

Describe the solution you'd like

I want easy ways of making sure that workflows were launched, that they contain the representables expected, that it's easy to swap out what their proceed function does from tests. I think we need to look closely at developer experience, what makes the library a joy to test?

Describe alternatives you've considered

Mocking? (Workflow is final)
Some horrible thing where I @testable import Workflow and try to pick at its innards? Not ideal...

Additional context

We should also stick to our creed about not just changing production code for the sake of tests, where reasonable.

SwiftUI Modals

  • add support for Modals

  • test permutations of Modal and NavLink

  • test multiple screens deep, similar to NavLink

  • test abandoning

Use SwiftCurrent Bot account for deployment credentials

Is your feature request related to a problem? Please describe.

Deployment credential access should not be tied to a single human

Describe the solution you'd like

A shared account that holds deployment credentials

Additional context

The created account's credentials should be stored in a Password Manager

Multiple Modal workflow does not abandon back to root

Describe the bug

When launching a workflow that has multiple screens presented modally, when calling abandon on the workflow, the stack of modals are not abandoned. Only the top modal is dismissed.

To Reproduce

Steps to reproduce the behavior:

  1. Create a Workflow with at least 2 modals
  2. Launch the workflow modaly
  3. complete the workflow and call abandon in the onFinish

Expected behavior

All modals are dismissed and I am back to the root view.

Screenshots

If applicable, add screenshots to help explain your problem.

Additional context

Add any other context about the problem here.

  • XCode Version: 12.5
  • Workflow Version: 3.0.1

Debug Logs

If applicable, add any debug logs that you have around the time of the problem.

Add DocC to the project and host it where I can read it.

Is your feature request related to a problem? Please describe.

I like the jazzy docs but I would get so much more from DocC with the articles and nicer layout. Also having those documents available to me locally would be helpful with getting on-boarded.

Describe the solution you'd like

A DocC created for the project or for each framework (whatever makes sense), and a website I can view that DocC.

Describe alternatives you've considered

Keep using Jazzy and the wiki. And then I'll just keep being sad.

Type safety on Workflow Launch Args

Is your feature request related to a problem? Please describe.

It's unfortunate that when I launch a Workflow the arguments I pass to launch it are not typed. So I don't get a compiler check.

Describe the solution you'd like

The short version? I want launch to be smart enough to know what the first item in the Workflow expects for its WorkflowInput and have that type be what I pass as args. Additionally if that type is Never I want to be forbidden from passing args and if that type is not Never I want to be forced to pass args.

Potential solution:
Combine has Publishers that end up chaining in an interesting way with Generics that we could rip off. For example

Workflow(FR1.self) creates a Workflow. But Workflow(FR1.self).thenProceed(with: FR2.self) creates Workflow<FR2, Workflow<FR1>> or...something like that? The solution is not totally ironed out in my head, but the premise is that you'd be able to backtrack through all the nested Workflows until you got to the first one, thus knowing the FlowRepresentable type that comes first and asserting launch happens with the correct input.

Describe alternatives you've considered

I suppose there's a simpler approach that is a little less consumer friendly where they just pass the FlowRepresentable type that is going to be launched and we steal the WorkflowInput from it. I'm not really a fan because it adds an unneeded parameter.

staysInViewStack .hiddenInitially is not setting the proceedInWorkflow when coming back to the screen

Describe the bug
Setting a .thenPresent to have a hiddenInitially seems to forget to set the proceedInWorkflow when you navigate back the view.

To Reproduce
Steps to reproduce the behavior:

  1. Setup a workflow with hiddenInitially
  2. Skip the hiddenInitially
  3. Navigate back to the hiddenInitially
  4. Try to proceed foorward

Expected behavior
Be able to move forward.

Additional context
Add any other context about the problem here.

  • XCode Version: 11.3.1
  • Workflow Version: 1.0.5

Sample:
See comments

Codecov report on README is lying

What happened?

Our codecov badge says 0% coverage but we have something like 90% coverage

Version

5.1.4 and 5.1.5

Relevant code sample

None

Relevant log output

None

Code of Conduct

  • I agree to follow this project's Code of Conduct

Testing stuff included in a pod for it

Is your feature request related to a problem? Please describe.
Testing things

Describe the solution you'd like
Include those testing mocks and listeners and asserters in a pod I can pull in on my test target

Describe alternatives you've considered
Copying everything from you and stealing it....

Additional context
God, Tyler. We've already talked about this.

Replace SonarCloud with CodeCov

Is your feature request related to a problem? Please describe.

SonarCloud was not usable by contributors outside of the WWT Organization. SonarCloud was also creating churn for the dev team via issues we did not want to fix and false positives on reports.

Describe the solution you'd like

We would like to not take on SonarCloud as a tool, but would like to see code coverage for code additions in PRs.

Describe alternatives you've considered

See discussion #48 for context.

For inspiration see https://github.com/Tyler-Keith-Thompson/CucumberSwift/blob/master/.github/.codecov.yml

[Bug]: UIKit FlowPersistence.persistWhenSkipped causes a flash

What happened?

In the UIKit example app there is a view that is persistWhenSkipped. When you go to a page that skips that view, you see a flash of content when the menu displays and then the next view slides over.

Simulator.Screen.Recording.-.iPod.touch.7th.generation.-.2022-03-01.at.14.22.57.mp4

Version

SwiftCurrent: 4.5.20

Relevant code sample

.thenProceed(with: SomeViewController.self, flowPersistence: .persistWhenSkipped)

Relevant log output

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Launch styles in SwiftUI

I want SwiftUI to have launch styles like UIKit so I can put my stuff in a navigation view.

I'd like something like:

WorkflowLauncher(isLaunched: .constant(true))
   .launchStyle(.navigationStack)
   .thenProceed(with: WorkflowItem(FR1.self).launchStyle(.modal()))

Describe alternatives you've considered

For Modals, I can embed them myself in sheets and share the binding with the WorkflowLauncher or set it on the view.
For navigation stacks, I'm not sure what I can do.

Abandoning and relaunching workflows in SwiftUI

Alternate title: State and State Objects and Instances oh my!

Describe the bug

Your template says a clear and concise description of the problem is necessary but I cannot deliver on that, so here is a rambling and difficult explanation of the problem:

Using our sample app I am able to consistently reproduce an interesting bug. Essentially by chaining workflows I am able to make the profile screen disappear, LIKE MAGIC! The branch meetup has this code reproduced. Short version: The profile screen has a workflow that isLaunched: .constant(true) and it abandons that workflow to logout.

While that part works beautifully then you proceed through your workflow again and after you login the profile screen just vanishes.

Debugging suggested to me that a fix might be finding a way (somehow...) to make the workflow a @State. I think that'll cause a host of other problems I don't want to think about, but I believe that is what needs to be done. This is more of a gut feeling but it's because instances of workflow just keep changing.

To Reproduce

Steps to reproduce the behavior:

  1. Go to profile
  2. Log in
  3. Log out
  4. Log in
  5. See error

Expected behavior

It should not do that.

Screenshots

Simulator.Screen.Recording.-.iPhone.12.Pro.Max.-.2021-07-29.at.09.39.54.mp4

SwiftUI: Abandon from outside `FlowRepresentable`

Is your feature request related to a problem? Please describe.

I've got multiple workflows in SwiftUI being presented at the same time. I'd like to abandon them all if a user is logged out. This is achievable in UIKit because you can just extract and use the data model. For example, I could've created multiple workflows and stored them somewhere, then just called abandon on them. However, because SwiftUI does not use the data model, that trick doesn't work.

Describe the solution you'd like

Realistically this is yet another example in my head of why we should allow the data model to be used in SwiftUI however this feature request is different. I'd like some way of abandoning a workflow from outside a FlowRepresentable think something like this:

WorkflowLauncher(isLaunched: .constant(true))
                .thenProceed(with: WorkflowItem(LoginView.self))
                .thenProceed(with: WorkflowItem(QRScannerFeatureOnboardingView.self))
                .thenProceed(with: WorkflowItem(QRScannerFeatureView.self))
                .abandonOn(user.isLoggedIn == false) // Version 1, Autoclosure
                .abandonOn { !user.isLoggedIn } // Version 2, for reals closure
                .tabItem {
                    Label("QR Scanner", systemImage: "camera")
                }
                .tag(Tab.qr)

That exact solution may not actually be possible, but you can see the advantage of describing this here.

Describe alternatives you've considered

Allow me to use data models:

This solution is a huge request, but it carries some very reasonable possibilities for me outside this one use-case, for example:

@State var workflow = Workflow(LoginView.self)
                                        .thenProceed(with: QRScannerFeatureOnboardingView.self)
                                        .thenProceed(with: QRScannerFeatureView.self)
                                        
// In the view
WorkflowLauncher(isLaunched: .constant(true))
                .launchInto(workflow)
                .tabItem {
                    Label("QR Scanner", systemImage: "camera")
                }
                .tag(Tab.qr)
                .onChange(of: user.isLoggedIn) { if !$0 { workflow.abandon() } } // abandons and relaunches, yay!

With how things are now:

The current alternative is for me to go to literally every FlowRepresentable view and add an onChange modifier that abandons the workflow if the user is not logged in anymore. This is highly unfortunate because then every view has to know about the user, even if it would otherwise not need to.

Additional context

There may be completely new options or patterns I have not yet thought through. I'm happy to have a discussion about that if my premise is incorrect.

Clear view stack when calling abandon on workflow that created the rootViewController

Is your feature request related to a problem? Please describe.
I have a custom UITabBarController that has a custom UINavigationController which I then use to launch into a predefined workflow. Under certain circumstances I want to relaunch the workflow with a new argument being passed in.
I would like abandon to clear the rootViewController from my custom navigation controller so that I have a clean slate for relaunching my workflow.

Describe the solution you'd like
I would like to be able to simply call
workflow.abandon(); viewcontroller?.launchInto(workflow, args: argy)
And when this is called, no matter how many times, I would only see the viewControllers defined in the workflow, and not any lingering view controllers.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
(viewController as? UINavigationController)?.viewControllers = []

Additional context
Reach out if you need more sample code.

Update styleguide to include a rule around annotations

Examples in the styleguide that involve annotations are displayed as

@State var someVariable: String

@obj private func someFunction() {
    /// ...
}

but this rule is not called out explicitly. In order to make it clearer that this is the expectation and to provide something to point to when not followed proposing to add a rule around annotations to the guide.

`_abandon` on `Workflow` not actually called.

Describe the bug

There's a deceptive bit of code that looks like it functions, but in real world conditions is silently failing.

To Reproduce

Steps to reproduce the behavior:

  1. Put a breakpoint on the _abandon function in Workflow.
  2. Abandon a workflow with a UIKit presenter.
  3. Notice how your breakpoint didn't get triggered in real world conditions? (tests may lie a bit, depending)

Expected behavior

_abandon should have been called

Additional context

A quick and dirty fix, that I haven't investigated enough yet:

extension AnyWorkflow {
    /**docs...*/
    public func abandon(animated: Bool = true, onFinish:(() -> Void)? = nil) {
        let abandonRef = _abandon // Use this in all code that called self?._abandon() since self had been de-initialized by the time the closure executes.
        if let presenter = orchestrationResponder as? UIKitPresenter {
            presenter.abandon(self, animated: animated) { [weak self] in
                // self?._abandon() // LIES! self is NIL HERE
                abandonRef()
                onFinish?()
            }
        }
        // ... more code is here
    }
}

SwiftUI NavigationLink

  • add support for NavLinks as launchStyle on WorkflowView and WorkflowItem

  • Add 10 screen test, beginning to end and back. don't forget to test wiggle/oscillation between 2 screens in the navigation view. what happens if someone hits back on nav view?

  • test abandoning

Whitesource flagging NPM dependency having a vulnerability

Describe the bug
Our project is being scanned by Whitesource and it is flagging this project with a vulnerability.

To Reproduce
Steps to reproduce the behavior:

  1. Scan your library with whitesource?
  2. See report?

Expected behavior
No vulnerabilities detected

Screenshots
[Redacted]

Additional context
As discussed earlier. Go fix please.

Memory Leak on Workflows

Describe the bug

We're leaking memory every time a workflow is launched and followed.

To Reproduce

Steps to reproduce the behavior:

  1. Create a workflow (ideally that autocompletes) for example a workflow that proceeds to the next item on viewDidLoad after 1 second.
  2. Launch that workflow multiple times
  3. Make sure the workflow has called onFinish. Bonus points: call abandon on it, NOTE: You should not have to in order to solve this problem, and calling abandon exposed an additional bug.
  4. Open the memory graph and cry, because the _WorkflowItems got retained when they should not have.

Expected behavior

A workflow should not create retain cycles, it's fine that it retains while executing, but when it is abandoned or when the same workflow is launched again it is reasonable that there aren't retain cycles.

NOTE: The library clears instances when you launch a Workflow. Is that behavior correct, or is that a bad assumption? It probably has limitations if a user launches the same instance of a workflow multiple times and expects it to just be duplicated. That's an odd thing for a user to expect, but the library does not support it currently. It assumes every call to launch means it can clear memory of all known instances.

Sonar Scan does not work for Pull Requests outside of WWT

Describe the bug

See PR #18 for an example of this. It seems that our testing of access to secrets is no longer correct and we will need to figure out how contributors can get a successful sonar scan.

To Reproduce

Steps to reproduce the behavior:

  1. Don't be a contributor on the project
  2. Fork the repo and change something minor like documentation
  3. Make a PR

Expected behavior

All parts of the pipeline pass

Screenshots

see PR #18

Debug Logs

Run sonarsource/sonarcloud-github-action@master
with:
projectBaseDir: .
env:
GITHUB_TOKEN: ***
SONAR_TOKEN:
/usr/bin/docker run --name a33c16c74351a4fd04c929e180db60e1f4978_331657 --label 8a33c1 --workdir /github/workspace --rm -e GITHUB_TOKEN -e SONAR_TOKEN -e INPUT_ARGS -e INPUT_PROJECTBASEDIR -e HOME -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RETENTION_DAYS -e GITHUB_ACTOR -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_OS -e RUNNER_TOOL_CACHE -e RUNNER_TEMP -e RUNNER_WORKSPACE -e ACTIONS_RUNTIME_URL -e ACTIONS_RUNTIME_TOKEN -e ACTIONS_CACHE_URL -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" -v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" -v "/home/runner/work/Workflow/Workflow":"/github/workspace" 8a33c1:6c74351a4fd04c929e180db60e1f4978
Set the SONAR_TOKEN env variable.

Launch into AnyWorkflow

Is your feature request related to a problem? Please describe.

I am using the Type erased AnyWorkflow, but I am unable to launch that erased workflow.

Describe the solution you'd like

Following a similar pattern to a Type Specified Workflow, I want to be able to call launchInto(AnyWorkFlow)

Describe alternatives you've considered

N/A

Additional context

N/A

Add convenience function to wrap a Workflow with Anyworkflow

Is your feature request related to a problem? Please describe.

To match the current pattern for building Workflows it would be great to be able to end the build chain with
eraseToAnyWorkflow. This would follow other modern swift paradigms (example AnyPublisher).

Describe the solution you'd like

Create an extension method on Workflow that will return the current Workflow erased as AnyWorkflow

Describe alternatives you've considered

N/A

Additional context

N/A

`Passthrough` FlowRepresentable

Is your feature request related to a problem? Please describe.

Not so much a problem, just an idea

Describe the solution you'd like

Idea: We could have a Passthrough type that's a little different than Never. Whereas Never means "I don't take in a value" or "I don't pass out a value". Passthrough could mean "I'll forward on anything that gets passed to me automatically`.

Hypothetical: Imagine a workflow wherein you've got a terms and conditions FlowRepresentable. Now it knows whether terms have been accepted or not, and it will make the decision to abandon whatever workflow it is in if the terms are not accepted. However if terms are accepted it might send that information to a server then move forward.

If that termsAndConditions had a Passthrough type for its input and output it would be even easier to stick those terms and conditions in any workflow while maintaining type safety of that flow.

Describe alternatives you've considered

I mean users can do this now, manually. So they could continue to do it manually. Something like:

struct FR1: FlowRepresentable {
    typealias WorkflowOutput = AnyWorkflow.PassedArgs
    var _workflowPointer: AnyFlowRepresentable?
    var passedArgs: AnyWorkflow.PassedArgs
    init(with args: AnyWorkflow.PassedArgs) {
        passedArgs = args
    }
    
    func doAThing() {
        proceedInWorkflow(passedArgs)
    }
}

That's just a lot of boilerplate

Finish up Github documentation

Is your feature request related to a problem? Please describe.

no

Describe the solution you'd like

A completed CONTRIBUTE.md and working templates.

Describe alternatives you've considered

None

Additional context

We've discussed that things are not complete. This issue is to help test the process.

removedAfterProceeding on last item in Workflow

Describe the bug

If you have .removedAfterProceeding as the FlowPersistence on an item in a workflow it does not properly remove that item (UIKit)

To Reproduce

Steps to reproduce the behavior:
Create 3 FlowRepresentables the last of which has a flowPersistence of .removedAfterProceeding
Call proceedInWorkflow() on all items in that flow
Observe after OnFinish is called the final UIViewController is still displayed

Expected behavior

The UIViewController should not be displayed after the onFinish block has executed

Screenshots

NO

Data Driven Workflows

This issue is here to help track some ideas and things related to the milestone. It's also here so the Milestone will stop bouncing between not-complete and complete.

So far we have spike branches: data-spike1 through data-spike3 so far.

The main PR that will bring in Data-Driven Workflows will be the branch data-driven

Update Sample App to Showcase "Edit" on Review

Our Sample app should show just how awesome this library is by adding "edit" functionality to the review screen.

If you edit your Location you might have to also edit some other downstream things, if you just edit your food choices, it might not. We can showcase just how powerful workflow is with that situation.

[Bug] [Triaged]: SwiftCurrent_CLI not building successfully in consuming iOS application

What happened?

While testing aspects of the new data-driven workflow feature, I created a brand new SwiftUI Project and added the SwiftCurrent, SwiftCurrent_SwiftUI, and SwiftCurrent_CLI dependencies via SPM.

Building after doing this resulted in 15 build errors in the SwiftCurrent_CLI target stemming from a library called "_InternalSwiftSyntaxParser". Likely due to this bit from the SwiftSyntax README:

Embedding SwiftSyntax in an Application

SwiftSyntax depends on the lib_InternalSwiftSyntaxParser.dylib/.so library which provides a C interface to the underlying Swift C++ parser. When you do swift build SwiftSyntax links and uses the library included in the Swift toolchain. If you are building an application make sure to embed _InternalSwiftSyntaxParser as part of your application's libraries.

You can either copy lib_InternalSwiftSyntaxParser.dylib/.so directly from the toolchain or even build it yourself from the Swift repository, as long as you are matching the same tags or branches in both the SwiftSyntax and Swift repositories. To build it for the host os (macOS/linux) use the following steps:

git clone https://github.com/apple/swift.git
./swift/utils/update-checkout --clone
./swift/utils/build-parser-lib --release --no-assertions --build-dir /tmp/parser-lib-build

Embedding in an iOS Application

You need to build lib_InternalSwiftSyntaxParser.dylib yourself, you cannot copy it from the toolchain. Follow the instructions above and change the invocation of build-parser-lib accordingly:

./swift/utils/build-parser-lib --release --no-assertions --build-dir /tmp/parser-lib-build-iossim --host iphonesimulator --architectures x86_64
./swift/utils/build-parser-lib --release --no-assertions --build-dir /tmp/parser-lib-build-ios --host iphoneos --architectures arm64

Version

SwiftCurrent Version 5.1.3
Xcode Version 13.3
macOS version 12.3

Relevant code sample

No response

Relevant log output

ld: warning: Could not find or use auto-linked library '_InternalSwiftSyntaxParser'
Undefined symbols for architecture x86_64:
  "_swiftparse_syntax_structure_versioning_identifier", referenced from:
      static SwiftSyntax.SyntaxParser.verifyNodeDeclarationHash() -> Swift.Bool in SwiftSyntax.o
  "_swiftparse_diagnostic_get_fixit", referenced from:
      closure #2 (Swift.UInt32) -> SwiftSyntax.FixIt in SwiftSyntax.Diagnostic.init(diag: Swift.UnsafeRawPointer, using: SwiftSyntax.SourceLocationConverter?) -> SwiftSyntax.Diagnostic in SwiftSyntax.o
  "_swiftparse_diagnostic_get_range", referenced from:
      closure #1 (Swift.UInt32) -> SwiftSyntax.SourceRange in SwiftSyntax.Diagnostic.init(diag: Swift.UnsafeRawPointer, using: SwiftSyntax.SourceLocationConverter?) -> SwiftSyntax.Diagnostic in SwiftSyntax.o
  "_swiftparse_diagnostic_get_message", referenced from:
      SwiftSyntax.Diagnostic.Message.init(Swift.UnsafeRawPointer) -> SwiftSyntax.Diagnostic.Message in SwiftSyntax.o
  "_swiftparse_diagnostic_get_range_count", referenced from:
      SwiftSyntax.Diagnostic.init(diag: Swift.UnsafeRawPointer, using: SwiftSyntax.SourceLocationConverter?) -> SwiftSyntax.Diagnostic in SwiftSyntax.o
  "_swiftparse_diagnostic_get_fixit_count", referenced from:
      SwiftSyntax.Diagnostic.init(diag: Swift.UnsafeRawPointer, using: SwiftSyntax.SourceLocationConverter?) -> SwiftSyntax.Diagnostic in SwiftSyntax.o
  "_swiftparse_parse_string", referenced from:
      closure #4 (Swift.UnsafePointer<Swift.Int8>) -> Swift.UnsafeMutableRawPointer? in static SwiftSyntax.SyntaxParser.(parseRaw in _FF480AC1827F0899EF1492D28392D5EA)(Swift.String, SwiftSyntax.IncrementalParseTransition?, Swift.String, SwiftSyntax.DiagnosticEngine?) -> SwiftSyntax.RawSyntax in SwiftSyntax.o
  "_swiftparse_diagnostic_get_source_loc", referenced from:
      SwiftSyntax.Diagnostic.init(diag: Swift.UnsafeRawPointer, using: SwiftSyntax.SourceLocationConverter?) -> SwiftSyntax.Diagnostic in SwiftSyntax.o
  "_swiftparse_diagnostic_get_severity", referenced from:
      closure #3 (Swift.UnsafeRawPointer?) -> () in static SwiftSyntax.SyntaxParser.(parseRaw in _FF480AC1827F0899EF1492D28392D5EA)(Swift.String, SwiftSyntax.IncrementalParseTransition?, Swift.String, SwiftSyntax.DiagnosticEngine?) -> SwiftSyntax.RawSyntax in SwiftSyntax.o
      SwiftSyntax.Diagnostic.Message.init(Swift.UnsafeRawPointer) -> SwiftSyntax.Diagnostic.Message in SwiftSyntax.o
  "_swiftparse_parser_create", referenced from:
      static SwiftSyntax.SyntaxParser.(parseRaw in _FF480AC1827F0899EF1492D28392D5EA)(Swift.String, SwiftSyntax.IncrementalParseTransition?, Swift.String, SwiftSyntax.DiagnosticEngine?) -> SwiftSyntax.RawSyntax in SwiftSyntax.o
  "_swiftparse_parser_set_node_handler", referenced from:
      static SwiftSyntax.SyntaxParser.(parseRaw in _FF480AC1827F0899EF1492D28392D5EA)(Swift.String, SwiftSyntax.IncrementalParseTransition?, Swift.String, SwiftSyntax.DiagnosticEngine?) -> SwiftSyntax.RawSyntax in SwiftSyntax.o
  "_swiftparse_parser_set_node_lookup", referenced from:
      static SwiftSyntax.SyntaxParser.(parseRaw in _FF480AC1827F0899EF1492D28392D5EA)(Swift.String, SwiftSyntax.IncrementalParseTransition?, Swift.String, SwiftSyntax.DiagnosticEngine?) -> SwiftSyntax.RawSyntax in SwiftSyntax.o
  "_swiftparse_parser_dispose", referenced from:
      $defer #1 () -> () in static SwiftSyntax.SyntaxParser.(parseRaw in _FF480AC1827F0899EF1492D28392D5EA)(Swift.String, SwiftSyntax.IncrementalParseTransition?, Swift.String, SwiftSyntax.DiagnosticEngine?) -> SwiftSyntax.RawSyntax in SwiftSyntax.o
  "_swiftparse_parser_set_diagnostic_handler", referenced from:
      static SwiftSyntax.SyntaxParser.(parseRaw in _FF480AC1827F0899EF1492D28392D5EA)(Swift.String, SwiftSyntax.IncrementalParseTransition?, Swift.String, SwiftSyntax.DiagnosticEngine?) -> SwiftSyntax.RawSyntax in SwiftSyntax.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Code of Conduct

  • I agree to follow this project's Code of Conduct

[Feature]: Server Driven Workflows in UIKit

What prompted this feature request?

I love SwiftUI but sometimes have to use UIKit and would love to use server driven workflows there too.

Describe the solution you'd like

I want to be able to use server driven payloads to create my Workflows

Describe alternatives you've considered

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Persistence in SwiftUI

Is your feature request related to a problem? Please describe.

In UIKit flowPersistence is a deliberately supported feature. in SwiftUI right now removedAfterProceeding is supported (partially by accident) and persistWhenSkipped really isn't. I'd like to see support for those, if possible.

Describe the solution you'd like

For default presentation:

removedAfterProceeding means the @State variable holding onto the view is cleared, this is mostly useful for animations.
persistWhenSkipped I do not believe has any real meaning here, if it's skipped with our default presentation mode then there's really nothing to persist.

For navigation links:

removedAfterProceeding in a perfect world this (somehow) animates in the next view then retroactively changes the back-stack to remove the item you just proceeded from. However I think that's impossible because of how animations for Navigation Views and back stack management work in SwiftUI. So I'm fine with the new view merely replacing the one that gets removed after proceeding.
persistWhenSkipped means that a skipped item is in the backstack. You can navigate backward to it, but when you navigate forward from the previous item it's not animated in. (Again, realizing that animations may be very hard to control here)

For modals:

removedAfterProceeding in a perfect world this (somehow) animates in the next view then retroactively changes the back-stack to remove the item you just proceeded from. However I think that's impossible because of back stack management work in SwiftUI. So I'm fine with the new view merely replacing the one that gets removed after proceeding.
persistWhenSkipped means that a skipped item is in the backstack. You can dismiss backward to it, but when you navigate forward from the previous item it's not animated in. (Again, realizing that animations may be very hard to control here)

Describe alternatives you've considered

Expressly have an error (ideally compile time) that stops users from being able to apply persistences that do not work.

Allow `thenProceed` in preview providers

Is your feature request related to a problem? Please describe.

I want to use previews to view my FlowRepresentable as it'll appear in a workflow, even if that's just a single item workflow. However, right now thenProceed is not available.

Describe the solution you'd like

Either make thenProceed globally available so we quit having one-off "I need it added" or add it to PreviewProvider.

Describe alternatives you've considered

I could write my own extension for this, but it should just work out of the box. Additionally, I suppose we could wrap thenProceed in some new protocol? However, I don't like the idea that I have to conform to a protocol just to define a workflow. There may be something to investigating resultBuilders in a future version of Swift where they allow those scoped functions. I've seen proposals about it, but for now I don't think we should go that route.

Relaunching workflow in SwiftUI when isLaunched is .constant(true)

Describe the bug

A clear and concise description of what the bug is.

To Reproduce

Steps to reproduce the behavior:

  1. Create a workflow that has an isLaunched value of .constant(true). Any of our tab view workflows in the sample app prove this.
  2. Abandon the workflow
  3. Witness the tab just completely disappearing instead of the workflow relaunching
  4. Cry

Expected behavior

I expected the same behavior as if I was not using a constant, if you abandon, then you set the value to true, the workflow is relaunched.

AnyView implementation for SwiftUI Presenter

[x] Testing
[x] Implementation
[x] Docs
[x] Example app

LIMIT: We do not need Modals or Navigation. Just view swapping.

This card is not getting merged into trunk until we have completed our investigation into NOT using AnyView (the card: Don't use AnyView for SwiftUI Presenter).

StoryboardLoadable protocol doesn't work with `AnyWorkflow.PassedArgs`

Woah...there was a deep underlying issue with SwiftCurrent altogether here. Short version? When the input type was AnyWorkflow.PassedArgs it was not using the factory method. Meaning all the StoryboardLoadable protocol things simply don't work with AnyWorkflow.PassedArgs, you can discover this yourself if you simply don't use PassthroughFlowRepresentable.

I can try to fix that as part of this issue, but it feels like this'll get stuck on behalf of a bug that has nothing to do with this new feature for a while.

Originally posted by @Tyler-Keith-Thompson in #79 (comment)

Extreme type safety!

Is your feature request related to a problem? Please describe.

Workflow has some sort of type safety at the moment and that's very helpful. We can take it much, much farther with the notion that compile-time errors are preferred.

Describe the solution you'd like

For example every FlowRepresentable can declare both an Input and Output, then when you create a workflow you can enforce (with a fluent API) that the next thing being presented has the same kind of Input that the last thing outputs.

Describe alternatives you've considered

We could not have type safety and just make everyone deal with "Any" for the rest of their lives, but that seems cruel.

Additional context

It's already done.....mostly.

[Feature]: Allow XCTAssertWorkflowLaunched to accept AnyWorkflow

What prompted this feature request?

Creating a data driven workflow gives me a type erased AnyWorkflow. I want to be able to write a unit test to ensure said workflow was launched, however XCTAssertWorkflowLaunched() is generic on the workflow's FlowRepresentable type and this can't be determined for a type erased workflow.

Describe the solution you'd like

Provide an overloaded non-generic version of XCTAssertWorkflowLaunched() that accepts AnyWorkflow.

Describe alternatives you've considered

N/A

Code of Conduct

  • I agree to follow this project's Code of Conduct

Document the Testing Package

Is your feature request related to a problem? Please describe.

There's a really great new testing package but no information about it.

Describe the solution you'd like

I want documentation available that tells me how to install and use the new testing package

Describe alternatives you've considered

We could have people randomly start typing code and praying it will work, but that seems like a poor alternative.

Additional context

  • It's worth discussing whether Jazzy should show any of this
  • It's worth discussing just how much you document about testing functions
  • Do the getting started guides go into much detail about it?
  • Testing is one of the things that sets us apart as a mature library you can trust, so there's something to capitalize on here

SwiftUI FullScreenCover support

Is your feature request related to a problem? Please describe.

#118 gives modal support with sheets, but I'd also like the ability to define full screen covers.

Describe the solution you'd like

Something like

WorkflowLauncher(isLaunched: .constant(true)) {
    thenProceed(with: FirstView.self) {
        thenProceed(with: SecondView.self).presentationType(.modal(.fullScreenCover))
    }
}

Describe alternatives you've considered

There's really not a good alternative if you're in the middle of a workflow.

Additional context

Unfortunately, ViewInspector does not support FullScreenCover at this time, so we will have to do without it.

[Bug]: Abandon does not dismiss SwiftUI Nav stack

What happened?

I've got a workflow set up with multiple items, I used the .embedInNavStack() fluent API, but when I call workflow?.abandon() the nav stack doesn't reset. I just stay on the last page.

Version

SwiftCurrent Version: 5.1.4
Xcode Version: 13.4.1
iOS Version: 14+

Relevant code sample

WorkflowView(launchingWith: .init(wrappedValue: appointment)) {
                WorkflowItem(FirstView.self)
                    .presentationType(.navigationLink)
                WorkflowItem(SecondView.self)
                    .presentationType(.navigationLink)
            }
            .embedInNavigationView()

// from inside SecondView
Button("Abandon") {
    workflow?.abandon()
}

Relevant log output

It seems that the body is being published. I suspect we need to listen to calls to `abandon` and then set all `isActive` flags to `false` inside of `WorkflowItemWrapper`

Code of Conduct

  • I agree to follow this project's Code of Conduct

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.