diff --git a/README.md b/README.md index f1b4ed7ee..4d19583d0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ The app is designed to give you an idea of what SwiftCurrent can do with minimal [We're working on it now!](https://github.com/wwt/SwiftCurrent/milestone/2) +If you would like to try the beta release, please install the `BETA_SwiftCurrent_SwiftUI` product in SPM or the `BETA_SwiftUI` sub spec in CocoaPods. For more detailed steps, [see our installation instructions](https://github.com/wwt/SwiftCurrent/wiki/Installation). See [Getting Started with SwiftUI](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-SwiftUI) for a quick tutorial. + +In order to use the library with SwiftUI, your minimum targeted versions must meet: iOS 14.0, macOS 11, tvOS 14.0, or watchOS 7.0. + +For us, beta means that the API may change without warning until the full release. However, we expect bugs to be at a minimum and documentation to be true and accurate. + # Quick Start This quick start uses SPM, but for other approaches, [see our installation instructions](https://github.com/wwt/SwiftCurrent/wiki/Installation). @@ -50,21 +56,60 @@ This quick start uses SPM, but for other approaches, [see our installation instr .product(name: "SwiftCurrent", package: "SwiftCurrent"), .product(name: "SwiftCurrent_UIKit", package: "SwiftCurrent") ``` -Then make your first FlowRepresentable view controller: +Then make your first FlowRepresentable view controllers: ```swift import SwiftCurrent import SwiftCurrent_UIKit -class ExampleViewController: UIWorkflowItem, FlowRepresentable { - override func viewDidLoad() { - view.backgroundColor = .green +class OptionalViewController: UIWorkflowItem, FlowRepresentable { + let input: String + required init(with args: String) { + input = args + super.init(nibName: nil, bundle: nil) } + required init?(coder: NSCoder) { nil } + override func viewDidLoad() { view.backgroundColor = .blue } + func shouldLoad() -> Bool { input.isEmpty } +} +class ExampleViewController: UIWorkflowItem, FlowRepresentable { + override func viewDidLoad() { view.backgroundColor = .green } } ``` -Then from your root view controller, call: +Then from your root view controller, call: ```swift import SwiftCurrent ... -launchInto(Workflow(ExampleViewController.self)) +let workflow = Workflow(OptionalViewController.self) + .thenProceed(with: ExampleViewController.self) +launchInto(workflow, args: "Skip optional screen") +``` + +And just like that you're started! + +## [BETA] SwiftUI + +```swift +.package(url: "https://github.com/wwt/SwiftCurrent.git", .upToNextMajor(from: "4.1.0")), +... +.product(name: "SwiftCurrent", package: "SwiftCurrent"), +.product(name: "BETA_SwiftCurrent_SwiftUI", package: "SwiftCurrent") +``` +Then make your first FlowRepresentable view: +```swift +import SwiftCurrent +struct ExampleView: View, FlowRepresentable { + weak var _workflowPointer: AnyFlowRepresentable? + let input: String + init(with args: String) { input = args } + var body: some View { Text("Passed in: \(input)") } + func shouldLoad() -> Bool { !input.isEmpty } +} +``` +Then from your ContentView body, add: +```swift +import SwiftCurrent_SwiftUI +... +WorkflowView(isLaunched: .constant(true), startingArgs: "Launched") + .thenProceed(with: WorkflowItem(ExampleView.self)) ``` And just like that you're started! @@ -73,10 +118,12 @@ And just like that you're started! - [Why SwiftCurrent?](https://github.com/wwt/SwiftCurrent/wiki/Why-This-Library%3F) - [Installation](https://github.com/wwt/SwiftCurrent/wiki/Installation) -- [Getting Started with Storyboards](https://github.com/wwt/SwiftCurrent/wiki/getting-started) -- [Getting Started with Programmatic UIKit](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-Programmatic-UIKit) +- [Getting Started with Storyboards](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-Storyboards) +- [Getting Started with Programmatic UIKit Views](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-Programmatic-UIKit-Views) +- [[BETA] Getting Started with SwiftUI](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-SwiftUI) - [Developer Documentation](https://wwt.github.io/SwiftCurrent/index.html) - [Upgrade Path](https://github.com/wwt/SwiftCurrent/blob/main/wiki/UPGRADE_PATH.md) +- [Contributing to SwiftCurrent](https://github.com/wwt/SwiftCurrent/blob/bb847f5934ebcb24c299aa0303974dd44c8d0c9c/.github/CONTRIBUTING.md) # Feedback @@ -85,4 +132,3 @@ If you like what you've seen, consider [giving us a star](https://github.com/wwt [![Stars](https://img.shields.io/github/stars/wwt/SwiftCurrent?style=social)](https://github.com/wwt/SwiftCurrent/stargazers) [![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2FSwiftCurrentWWT)](https://twitter.com/SwiftCurrentWWT) - diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started-Storyboard.md similarity index 98% rename from wiki/Getting-Started.md rename to wiki/Getting-Started-Storyboard.md index 09e68ddcb..b3ab078c5 100644 --- a/wiki/Getting-Started.md +++ b/wiki/Getting-Started-Storyboard.md @@ -1,8 +1,8 @@ -# Swift Package Manager with Storyboards +## Overview This guide will walk you through getting a [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) up and running in a new iOS project. If you would like to see an existing project, clone the repo and view the `SwiftCurrentExample` scheme in `SwiftCurrent.xcworkspace`. -The app in this guide is going to be very simple. It consists of a screen that will launch the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html), a screen to enter an email address, and an optional screen for if your email contains `wwt.com`. Here is a preview of what the app will look like: +The app in this guide is going to be very simple. It consists of a screen that will launch the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html), a screen to enter an email address, and an optional screen for when the user enters an email with `@wwt.com` in it. Here is a preview of what the app will look like: ![Preview image of app](https://github.com/wwt/SwiftCurrent/blob/e863b8eaa91a06d7bf3f236b9dc74d2b8342a8f6/wiki/storyboard.gif) diff --git a/wiki/Getting-Started-SwiftUI.md b/wiki/Getting-Started-SwiftUI.md new file mode 100644 index 000000000..6b8c52765 --- /dev/null +++ b/wiki/Getting-Started-SwiftUI.md @@ -0,0 +1,163 @@ +## Overview + +This guide will walk you through getting a [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) up and running in a new iOS project. + +The app in this guide is going to be very simple. It consists of a view that will host the [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html), a view to enter an email address, and an optional view for when the user enters an email with `@wwt.com` in it. Here is a preview of what the app will look like: + +![Preview image of app](https://github.com/wwt/SwiftCurrent/blob/36c45fcc3cc66dba16d5d5c78bcd4bc865175a34/wiki/swiftUI.gif) + +## Adding the dependency + +For instructions on SPM and CocoaPods, [check out our installation page.](https://github.com/wwt/SwiftCurrent/wiki/Installation#swift-package-manager) + +## IMPORTANT NOTE + +SwiftCurrent is so convenient that you may miss the couple lines that are calls to the library. To make it easier, we've marked our code snippets with `// SwiftCurrent` to highlight items that are coming from the library. + +## Create your views + +Create two views that implement [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html). + +```swift +import SwiftUI +import SwiftCurrent + +struct FirstView: View, FlowRepresentable { // SwiftCurrent + typealias WorkflowOutput = String // SwiftCurrent + weak var _workflowPointer: AnyFlowRepresentable? // SwiftCurrent + + @State private var email = "" + private let name: String + + init(with name: String) { // SwiftCurrent + self.name = name + } + + var body: some View { + VStack { + Text("Welcome \(name)!") + TextField("Enter email...", text: $email) + .textContentType(.emailAddress) + Button("Save") { proceedInWorkflow(email) } + } + } +} + +struct FirstView_Previews: PreviewProvider { + static var previews: some View { + FirstView(with: "Example Name") + } +} + +struct SecondView: View, FlowRepresentable { // SwiftCurrent + typealias WorkflowOutput = String // SwiftCurrent + weak var _workflowPointer: AnyFlowRepresentable? // SwiftCurrent + + private let email: String + + init(with email: String) { // SwiftCurrent + self.email = email + } + + var body: some View { + VStack { + Button("Finish") { proceedInWorkflow(email) } + } + } + + func shouldLoad() -> Bool { // SwiftCurrent + email.lowercased().contains("@wwt.com") + } +} + +struct SecondView_Previews: PreviewProvider { + static var previews: some View { + SecondView(with: "Example.Name@wwt.com") + } +} +``` + +### Let's talk about what is going on with these views + +#### **Why is `_workflowPointer` weak?** + +
+ +The [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) protocol requires there to be a `_workflowPointer` on your object, but protocols cannot enforce you to use `weak`. If you do not put `weak var _workflowPointer`, the [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) will end up with a strong circular reference when placed in a [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html). +
+ +#### **What's this `shouldLoad()`?** + +
+ +It is part of the [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) protocol. It has default implementations created for your convenience but is still implementable if you want to control when a [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) should load in the workflow. It is called after `init` but before `body` in SwiftUI. +
+ +#### **Why is there a `WorkflowOutput` but no `WorkflowInput`?** + +
+ +`WorkflowInput` is inferred from the initializer that you create. If you do not include an initializer, `WorkflowInput` will be `Never`; otherwise `WorkflowInput` will be the type supplied in the initializer. `WorkflowOutput` cannot be inferred to be anything other than `Never`. This means you must manually provide `WorkflowOutput` a type when you want to pass data forward. +
+ +## Launching the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) + +Next we add a [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html) to the body of our starting app view, in this case `ContentView`. + +```swift +import SwiftUI +import SwiftCurrent_SwiftUI + +struct ContentView: View { + @State var workflowIsPresented = false + var body: some View { + if !workflowIsPresented { + Button("Present") { $workflowIsPresented.wrappedValue = true } + } + WorkflowView(isLaunched: $workflowIsPresented, startingArgs: "SwiftCurrent") // SwiftCurrent + .thenProceed(with: WorkflowItem(FirstView.self) // SwiftCurrent + .applyModifiers { firstView in firstView.padding().border(.gray) }) + .thenProceed(with: WorkflowItem(SecondView.self) // SwiftCurrent + .applyModifiers { $0.padding().border(.gray) }) + .onFinish { passedArgs in // SwiftCurrent + workflowIsPresented = false + guard case .args(let emailAddress as String) = passedArgs else { + print("No email address supplied") + return + } + print(emailAddress) + } + } +} + +struct Content_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} +``` + +### Let's discuss what's going on here + +#### **Wait, where is the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html)?** + +
+ +In SwiftUI, the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) type is handled by the [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html). That view contains an underlying [AnyWorkflow](https://wwt.github.io/SwiftCurrent/Classes/AnyWorkflow.html) that it manages and exposes as appropriate. +
+ +#### **Where is the type safety, I heard about?** + +
+ +[WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html) is specialized with your `startingArgs` type. In [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html), these types are supplied by the `WorkflowInput` and `WorkflowOutput` associated types. These all work together to create compile-time type safety when creating your flow. This means that you will get a build error if the output of `FirstView` does not match the input type of `SecondView`. +
+ +#### **What's going on with this `startingArgs` and `passedArgs`?** + +
+ +`startingArgs` are the [AnyWorkflow.PassedArgs](https://wwt.github.io/SwiftCurrent/Classes/AnyWorkflow/PassedArgs.html) handed to the first [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) in the workflow. These arguments are used to pass data and determine if the view should load. + +`passedArgs` are the [AnyWorkflow.PassedArgs](https://wwt.github.io/SwiftCurrent/Classes/AnyWorkflow/PassedArgs.html) coming from the last view in the workflow. `onFinish` is only called when the user has gone through all the screens in the `Workflow` by navigation or skipping. For this workflow, `passedArgs` is going to be the output of `FirstView` or `SecondView` depending on the email signature typed in `FirstView`. To extract the value, we unwrap the variable within the case of `.args()` as we expect this workflow to return some argument. +
diff --git a/wiki/Getting-Started-UIKit.md b/wiki/Getting-Started-UIKit.md index 29d81d9f5..714add626 100644 --- a/wiki/Getting-Started-UIKit.md +++ b/wiki/Getting-Started-UIKit.md @@ -1,8 +1,8 @@ -# Swift Package Manager with Programmatic UIKit Views +## Overview This guide will walk you through getting a [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) up and running in a new iOS project. If you would like to see an existing project, clone the repo and view the `SwiftCurrentExample` scheme in `SwiftCurrent.xcworkspace`. -The app in this guide is going to be very simple. It consists of a screen that will launch the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html), a screen to enter an email address, and an optional screen for if your email contains `wwt.com`. Here is a preview of what the app will look like: +The app in this guide is going to be very simple. It consists of a screen that will launch the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html), a screen to enter an email address, and an optional screen for when the user enters an email with `@wwt.com` in it. Here is a preview of what the app will look like: ![Preview image of app](https://github.com/wwt/SwiftCurrent/blob/04a4fdb0c1d5848b6d43174168b51f95c78b2785/wiki/programmatic.gif) diff --git a/wiki/Home.md b/wiki/Home.md index edd058e69..18b53cb91 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -1,5 +1,6 @@ - [Why This Library?](https://github.com/wwt/SwiftCurrent/wiki/Why-This-Library%3F) - [Installation](https://github.com/wwt/SwiftCurrent/wiki/Installation) -- [Getting Started with Storyboards](https://github.com/wwt/SwiftCurrent/wiki/getting-started) -- [Getting Started with Programmatic UIKit](https://github.com/wwt/SwiftCurrent/wiki/getting-started-with-programmatic-uikit) +- [Getting Started with Storyboards](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-Storyboards) +- [Getting Started with Programmatic UIKit Views](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-Programmatic-UIKit-Views) +- [[BETA] Getting Started with SwiftUI](https://github.com/wwt/SwiftCurrent/wiki/Getting-Started-with-SwiftUI) - [Developer Documentation](https://wwt.github.io/SwiftCurrent/index.html) \ No newline at end of file diff --git a/wiki/Installation.md b/wiki/Installation.md index a4e0441e1..328c26429 100644 --- a/wiki/Installation.md +++ b/wiki/Installation.md @@ -19,6 +19,7 @@ Add the following products to your target dependencies. ```swift .product(name: "SwiftCurrent", package: "SwiftCurrent"), .product(name: "SwiftCurrent_UIKit", package: "SwiftCurrent") +.product(name: "BETA_SwiftCurrent_SwiftUI", package: "SwiftCurrent") ``` ### Your import statements for these products will be @@ -26,10 +27,12 @@ Add the following products to your target dependencies. ```swift import SwiftCurrent import SwiftCurrent_UIKit +import SwiftCurrent_SwiftUI // BETA ``` `SwiftCurrent_UIKit` will need to target a platform that supports UIKit, such as iOS or macOS with Catalyst. +[BETA] `SwiftCurrent_SwiftUI` requires the minimum versions of iOS 14.0, macOS 11, tvOS 14.0, or watchOS 7.0. # CocoaPods @@ -41,6 +44,11 @@ Set up [CocoaPods](https://cocoapods.org/) for your project, then include SwiftC pod 'SwiftCurrent/UIKit' ``` +### [BETA] To use SwiftCurrent with SwiftUI +```ruby +pod 'SwiftCurrent/BETA_SwiftUI' +``` + ### Your import statement will be ```swift diff --git a/wiki/swiftUI.gif b/wiki/swiftUI.gif new file mode 100644 index 000000000..ad607014d Binary files /dev/null and b/wiki/swiftUI.gif differ