Skip to content

Commit

Permalink
wi
Browse files Browse the repository at this point in the history
    - SwiftUINavigationCore
  • Loading branch information
stephencelis committed Jul 29, 2024
1 parent c84d5f4 commit 1e02624
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 84 deletions.
99 changes: 42 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,36 @@ Tools for making navigation in Swift applications simpler, more precise, and mor
## Overview

This library contains a suite of tools that form the foundation for building powerful state
management and navigation APIs for Apple platforms, such as SwiftUI, UIKit, AppKit, and on non-Apple
platforms, such as Windows, Linux, Wasm and more.
management and navigation APIs for Apple platforms, such as SwiftUI, UIKit, and AppKit, as well as
for non-Apple platforms, such as Windows, Linux, Wasm, and more.

The SwiftNavigation library forms the foundation that more advanced tools can be built upon, such
as the UIKitNavigation and SwiftUINavigation libraries. There are two primary tools provided:

* ``observe(_:)``: Minimally observe changes in a model.
* ``UIBinding``: Two-way binding for connecting navigation and UI components to an observable model.
* `observe`: Minimally observe changes in a model.
* `UIBinding`: Two-way binding for connecting navigation and UI components to an observable model.

In addition to these tools there are some supplementary concepts that allow you to build more
powerful tools, such as ``UITransaction``, which associates animations and other data with state
changes, and ``UINavigationPath``, which is a type-erased stack of data that helps in describing
powerful tools, such as `UITransaction`, which associates animations and other data with state
changes, and `UINavigationPath`, which is a type-erased stack of data that helps in describing
stack-based navigation.

All of these tools form the foundation for how one can build more powerful and robust tools for
SwiftUI, UIKit, AppKit, and even non-Apple platforms:
SwiftUI, UIKit, AppKit, and even non-Apple platforms.

#### SwiftUI

> [!IMPORTANT]
> To get access to the tools described below you must depend on the SwiftUINavigation package and
> To get access to the tools described below you must depend on the SwiftNavigation package and
> import SwiftUINavigation.
SwiftUI already comes with incredibly powerful navigation APIs, but there are a few areas lacking
that can be filled. In particular, driving navigation from enum state so that you can have
compile-time guarantees that only one destination can be active at a time.

For example, suppose you have a feature that can present a sheet for creating an item,
drill-down to a view for editing an item, and can present an alert for confirming to delete an
item. One can technically model this with 3 separate optionals:
For example, suppose you have a feature that can present a sheet for creating an item, drill-down to
a view for editing an item, and can present an alert for confirming to delete an item. One can
technically model this with 3 separate optionals:

```swift
@Observable
Expand All @@ -50,8 +50,8 @@ class FeatureModel {
}
```

And then in the view one can use the `sheet`, `navigationDestination` and `alert` view modifiers
to describe the type of navigation:
And then in the view one can use the `sheet`, `navigationDestination` and `alert` view modifiers to
describe the type of navigation:

```swift
.sheet(item: $model.addItem) { addItemModel in
Expand All @@ -67,14 +67,13 @@ to describe the type of navigation:
```

This works great at first, but also introduces a lot of unnecessary complexity into your feature.
These 3 optionals means that there is technically 8 different states: All can be `nil`, one can
These 3 optionals means that there are technically 8 different states: All can be `nil`, one can
be non-`nil`, two could be non-`nil`, or all three could be non-`nil`. But only 4 of those states
are valid: either all are `nil` or exactly one is non-`nil`.

By allowing these 4 other invalid states we can accidentally tell SwiftUI to both present a sheet
and alert at the same time. But that is not a valid thing to do in SwiftUI, and SwiftUI will even
print a message to the console letting you know that in the future it may actually crash your
app.
and alert at the same time, but that is not a valid thing to do in SwiftUI, and SwiftUI will even
print a message to the console letting you know that in the future it may actually crash your app.

Luckily Swift comes with the perfect tool for dealing with this kind of situation: enums! They
allow you to concisely define a type that can be one of many cases. So, we can refactor our 3
Expand All @@ -95,7 +94,7 @@ class FeatureModel {

This is more concise, and we get compile-time verification that at most one destination can be
active at a time. However, SwiftUI does not come with the tools to drive navigation from this
model. This is where the SwiftUINavigation becomes useful.
model. This is where the SwiftUINavigation tools becomes useful.

We start by annotating the `Destination` enum with the `@CasePathable` macro, which allows one to
refer to the cases of an enum with dot-syntax just like one does with structs and properties:
Expand Down Expand Up @@ -133,12 +132,12 @@ we can still use SwiftUI's navigation APIs.
#### UIKit

> [!IMPORTANT]
> To get access to the tools described below you must depend on the UIKitNavigation package and
> To get access to the tools described below you must depend on the SwiftNavigation package and
> import UIKitNavigation.
Unlike SwiftUI, UIKit does not come with state-driven navigation tools. Its navigation tools are
"fire-and-forget", meaning you simply invoke a method to trigger a navigation, but there is
no representation of that even in your feature's state.
no representation of that in your feature's state.

For example, to present a sheet from a button press one can simply do:

Expand All @@ -150,13 +149,13 @@ let button = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in

This makes it easy to get started with navigation, but as SwiftUI has taught us, it is incredibly
powerful to be able to drive navigation from state. It allows you to encapsulate more of your
feature's logic in an isolated and testable domain, and it unlocks deep linking for free since
one just needs to construct a piece of state that represents where you want to navigate to, hand
it to SwiftUI, and let SwiftUI handle the rest.
feature's logic in an isolated and testable domain, and it unlocks deep linking for free since one
just needs to construct a piece of state that represents where you want to navigate to, hand it to
SwiftUI, and let SwiftUI handle the rest.

The UIKitNavigation library brings a powerful suite of navigation tools to UIKit that are heavily
inspired by SwiftUI. For example, if you have a feature model like the one discussed above in
the <doc:SwiftNavigation#SwiftUI> section:
the [SwiftUI](#swiftui) section:

```swift
@Observable
Expand All @@ -171,7 +170,7 @@ class FeatureModel {
}
```

…then one can drive navigation to
…then one can drive navigation in a _view controller_ using tools in the library:

```swift
class FeatureViewController: UIViewController {
Expand Down Expand Up @@ -204,18 +203,18 @@ triggered, and when the presented view is dismissed, the state will be `nil`'d o

All of these tools are built on top of Swift's powerful Observation framework. However, that
framework only works on newer versions of Apple's platforms: iOS 17+, macOS 14+, tvOS 17+ and
watchOS 10+. However, thanks to our backport of Swift's observation tools (see
watchOS 10+. However, thanks to our back-port of Swift's observation tools (see
[Perception](http://github.com/pointfreeco/swift-perception)), you can make use of our tools
right away, going all the way back to iOS 13.
right away, going all the way back to the iOS 13 era of platforms.

#### Non-Apple platforms

These tools can also form the foundation of building navigation tools for non-Apple platforms,
such as Windows, Linux, Wasm and more. We do not currently provide any such tools at this moment,
but it is possible for them to be built externally.
These tools can also form the foundation of building navigation tools for non-Apple platforms, such
as Windows, Linux, Wasm and more. We do not currently provide any such tools at this moment, but it
is possible for them to be built externally.

For example, in Wasm it is possible to use the ``observe(_:)`` function to observe changes to a
model and update the DOM:
For example, in Wasm it is possible to use the `observe` function to observe changes to a model and
update the DOM:

```swift
import JavaScriptKit
Expand All @@ -239,14 +238,10 @@ alert(isPresented: $model.isAlertPresented) // TODO
This repo comes with lots of examples to demonstrate how to solve common and complex navigation
problems with the library. Check out [this](./Examples) directory to see them all, including:

* [Case Studies](./Examples/CaseStudies)
* Alerts & Confirmation Dialogs
* Sheets & Popovers & Fullscreen Covers
* Navigation Links
* Routing
* Custom Components
* [Inventory](./Examples/Inventory): A multi-screen application with lists, sheets, popovers and
alerts, all driven by state and deep-linkable.
* [Case Studies](./Examples/CaseStudies): A collection of SwiftUI and UIKit case studies
demonstrating this library's APIs.
* [Inventory](./Examples/Inventory): A multi-screen application with lists, sheets, popovers and
alerts, all driven by state and deep-linkable.

## Learn More

Expand All @@ -263,8 +258,8 @@ You can watch all of the episodes [here](https://www.pointfree.co/collections/sw

## Community

If you want to discuss this library or have a question about how to use it to solve
a particular problem, there are a number of places you can discuss with fellow
If you want to discuss this library or have a question about how to use it to solve a particular
problem, there are a number of places you can discuss with fellow
[Point-Free](http://www.pointfree.co) enthusiasts:

* For long-form discussions, we recommend the
Expand All @@ -276,32 +271,22 @@ a particular problem, there are a number of places you can discuss with fellow

You can add SwiftUI Navigation to an Xcode project by adding it as a package dependency.

> https://github.com/pointfreeco/swiftui-navigation
> https://github.com/pointfreeco/swift-navigation
If you want to use SwiftUI Navigation in a [SwiftPM](https://swift.org/package-manager/) project,
If you want to use Swift Navigation in a [SwiftPM](https://swift.org/package-manager/) project,
it's as simple as adding it to a `dependencies` clause in your `Package.swift`:

``` swift
dependencies: [
.package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "1.0.0")
.package(url: "https://github.com/pointfreeco/swift-navigation", from: "2.0.0")
]
```

## Documentation

The latest documentation for the SwiftUI Navigation APIs is available
[here](https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation).
The latest documentation for the Swift Navigation APIs is available
[here](https://swiftpackageindex.com/pointfreeco/swift-navigation/main/documentation/swiftnavigation).

## License

This library is released under the MIT license. See [LICENSE](LICENSE) for details.

[NavigationLink.init]: https://developer.apple.com/documentation/swiftui/navigationlink/init(destination:label:)-27n7s
[TabView.init]: https://developer.apple.com/documentation/swiftui/tabview/init(content:)
[case-paths-gh]: https://github.com/pointfreeco/swift-case-paths
[what-is-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/whatisnavigation
[nav-links-dests-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/navigation
[sheets-popovers-covers-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/sheetspopoverscovers
[alerts-dialogs-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/alertsdialogs
[bindings]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/bindings
[docs]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation
53 changes: 26 additions & 27 deletions Sources/SwiftNavigation/Documentation.docc/SwiftNavigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ Tools for making navigation in Swift applications simpler, more precise, and mor
## Overview

This library contains a suite of tools that form the foundation for building powerful state
management and navigation APIs for Apple platforms, such as SwiftUI, UIKit, AppKit, and on non-Apple
platforms, such as Windows, Linux, Wasm and more.
management and navigation APIs for Apple platforms, such as SwiftUI, UIKit, and AppKit, as well as
for non-Apple platforms, such as Windows, Linux, Wasm, and more.

The SwiftNavigation library forms the foundation that more advanced tools can be built upon, such
as the UIKitNavigation and SwiftUINavigation libraries. There are two primary tools provided:
Expand All @@ -20,20 +20,20 @@ changes, and ``UINavigationPath``, which is a type-erased stack of data that hel
stack-based navigation.

All of these tools form the foundation for how one can build more powerful and robust tools for
SwiftUI, UIKit, AppKit, and even non-Apple platforms:
SwiftUI, UIKit, AppKit, and even non-Apple platforms.

#### SwiftUI

> Important: To get access to the tools described below you must depend on the SwiftUINavigation
> Important: To get access to the tools described below you must depend on the SwiftNavigation
> package and import SwiftUINavigation.
SwiftUI already comes with incredibly powerful navigation APIs, but there are a few areas lacking
that can be filled. In particular, driving navigation from enum state so that you can have
compile-time guarantees that only one destination can be active at a time.

For example, suppose you have a feature that can present a sheet for creating an item,
drill-down to a view for editing an item, and can present an alert for confirming to delete an
item. One can technically model this with 3 separate optionals:
For example, suppose you have a feature that can present a sheet for creating an item, drill-down to
a view for editing an item, and can present an alert for confirming to delete an item. One can
technically model this with 3 separate optionals:

```swift
@Observable
Expand Down Expand Up @@ -61,14 +61,13 @@ to describe the type of navigation:
```

This works great at first, but also introduces a lot of unnecessary complexity into your feature.
These 3 optionals means that there is technically 8 different states: All can be `nil`, one can
These 3 optionals means that there are technically 8 different states: All can be `nil`, one can
be non-`nil`, two could be non-`nil`, or all three could be non-`nil`. But only 4 of those states
are valid: either all are `nil` or exactly one is non-`nil`.

By allowing these 4 other invalid states we can accidentally tell SwiftUI to both present a sheet
and alert at the same time. But that is not a valid thing to do in SwiftUI, and SwiftUI will even
print a message to the console letting you know that in the future it may actually crash your
app.
and alert at the same time, but that is not a valid thing to do in SwiftUI, and SwiftUI will even
print a message to the console letting you know that in the future it may actually crash your app.

Luckily Swift comes with the perfect tool for dealing with this kind of situation: enums! They
allow you to concisely define a type that can be one of many cases. So, we can refactor our 3
Expand All @@ -88,8 +87,8 @@ class FeatureModel {
```

This is more concise, and we get compile-time verification that at most one destination can be
active at a time. However, SwiftUI does not come with the tools to drive navigation from this
model. This is where the SwiftUINavigation becomes useful.
active at a time. However, SwiftUI does not come with the tools to drive navigation from this model.
This is where the SwiftUINavigation tools becomes useful.

We start by annotating the `Destination` enum with the `@CasePathable` macro, which allows one to
refer to the cases of an enum with dot-syntax just like one does with structs and properties:
Expand Down Expand Up @@ -118,19 +117,19 @@ the `destination` property:
```

> Note: For the alert we are using the special `Binding` initializer that turns a `Binding<Void?>`
into a `Binding<Bool>`.
> into a `Binding<Bool>`.
We now have a concise way of describing all of the destinations a feature can navigate to, and
we can still use SwiftUI's navigation APIs.

#### UIKit

> Important: To get access to the tools described below you must depend on the UIKitNavigation
package and import UIKitNavigation.
> Important: To get access to the tools described below you must depend on the SwiftNavigation
> package and import UIKitNavigation.
Unlike SwiftUI, UIKit does not come with state-driven navigation tools. Its navigation tools are
"fire-and-forget", meaning you simply invoke a method to trigger a navigation, but there is
no representation of that even in your feature's state.
no representation of that in your feature's state.

For example, to present a sheet from a button press one can simply do:

Expand All @@ -142,9 +141,9 @@ let button = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in

This makes it easy to get started with navigation, but as SwiftUI has taught us, it is incredibly
powerful to be able to drive navigation from state. It allows you to encapsulate more of your
feature's logic in an isolated and testable domain, and it unlocks deep linking for free since
one just needs to construct a piece of state that represents where you want to navigate to, hand
it to SwiftUI, and let SwiftUI handle the rest.
feature's logic in an isolated and testable domain, and it unlocks deep linking for free since one
just needs to construct a piece of state that represents where you want to navigate to, hand it to
SwiftUI, and let SwiftUI handle the rest.

The UIKitNavigation library brings a powerful suite of navigation tools to UIKit that are heavily
inspired by SwiftUI. For example, if you have a feature model like the one discussed above in
Expand All @@ -163,7 +162,7 @@ class FeatureModel {
}
```

…then one can drive navigation to
…then one can drive navigation in a _view controller_ using tools in the library:

```swift
class FeatureViewController: UIViewController {
Expand Down Expand Up @@ -196,18 +195,18 @@ triggered, and when the presented view is dismissed, the state will be `nil`'d o

All of these tools are built on top of Swift's powerful Observation framework. However, that
framework only works on newer versions of Apple's platforms: iOS 17+, macOS 14+, tvOS 17+ and
watchOS 10+. However, thanks to our backport of Swift's observation tools (see
watchOS 10+. However, thanks to our back-port of Swift's observation tools (see
[Perception](http://github.com/pointfreeco/swift-perception)), you can make use of our tools
right away, going all the way back to iOS 13.
right away, going all the way back to the iOS 13 era of platforms.

#### Non-Apple platforms

These tools can also form the foundation of building navigation tools for non-Apple platforms,
such as Windows, Linux, Wasm and more. We do not currently provide any such tools at this moment,
but it is possible for them to be built externally.
These tools can also form the foundation of building navigation tools for non-Apple platforms, such
as Windows, Linux, Wasm and more. We do not currently provide any such tools at this moment, but it
is possible for them to be built externally.

For example, in Wasm it is possible to use the ``observe(_:isolation:)`` function to observe changes
to a model and update the DOM:
to a model and update the DOM:

```swift
import JavaScriptKit
Expand Down

0 comments on commit 1e02624

Please sign in to comment.