Skip to content

Commit

Permalink
Merge pull request #74 from wwt/swiftui-docs
Browse files Browse the repository at this point in the history
SwiftUI Documentation
  • Loading branch information
morganzellers authored Jul 20, 2021
2 parents bb847f5 + 0da0d4b commit 02a6340
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 15 deletions.
64 changes: 55 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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<Never, Never>, FlowRepresentable {
override func viewDidLoad() {
view.backgroundColor = .green
class OptionalViewController: UIWorkflowItem<String, Never>, 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<Never, Never>, 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!
Expand All @@ -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

Expand All @@ -85,4 +132,3 @@ If you like what you've seen, consider [giving us a star](https://github.com/wwt
<!-- Social Media -->
[![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)

Original file line number Diff line number Diff line change
@@ -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)

Expand Down
163 changes: 163 additions & 0 deletions wiki/Getting-Started-SwiftUI.md
Original file line number Diff line number Diff line change
@@ -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: "[email protected]")
}
}
```

### Let's talk about what is going on with these views

#### **Why is `_workflowPointer` weak?**

<details>

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).
</details>

#### **What's this `shouldLoad()`?**

<details>

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.
</details>

#### **Why is there a `WorkflowOutput` but no `WorkflowInput`?**

<details>

`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.
</details>

## 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)?**

<details>

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.
</details>

#### **Where is the type safety, I heard about?**

<details>

[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`.
</details>

#### **What's going on with this `startingArgs` and `passedArgs`?**

<details>

`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.
</details>
4 changes: 2 additions & 2 deletions wiki/Getting-Started-UIKit.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
5 changes: 3 additions & 2 deletions wiki/Home.md
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 8 additions & 0 deletions wiki/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ 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

```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

Expand All @@ -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
Expand Down
Binary file added wiki/swiftUI.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 02a6340

Please sign in to comment.