Preamble To celebrate the release of Swift macros we releasing updates to 4 of our popular libraries to greatly simplify and enhance their abilities: CasePaths, ComposableArchitecture, SwiftUINavigation, and Dependencies. Each day this week we will detail how macros have allowed us to massively simplify one of these libraries, and increase their powers.
Today we are releasing version 1.1 of our popular library, SwiftUINavigation, which is a collection of tools that help you better model navigation using enums. This release does not introduce a macro to the library itself, but it does heavily make use of the new @CasePathable
macro that we discussed earlier this week. We can now greatly simplify how you interact with SwiftUI navigation view modifiers while still modeling your domains as concisely as possible with enums.
Join us for a quick overview of the new tools, and be sure to update to version 1.1 of the library to take advantage of these tools.
The SwiftUINavigation library provides tools that allow you to drive navigation in your features using a single enum. This makes it possible to prove at compile time that only a single destination can be active at a time, helping reduce the complexity of your features.
For example, if you have an observable model for a meeting that is capable of showing an edit feature in a sheet, drilling down to a record meeting feature, or showing an alert, then an optimal way to design this domain is the following:
@Observable
class MeetingDetailModel {
var destination: Destination?
enum Destination {
case alert(AlertState<AlertAction>)
case edit(EditMeetingModel)
case record(RecordMeetingModel)
}
…
}
The single piece of optional destination
state determines whether or not we are currently navigated to a particular feature.
This can be powerful, but unfortunately vanilla SwiftUI does not provide the tools to drive navigation off of such a domain. Its tools, such as the sheet
, alert
and navigationDestination
view modifiers, are tuned for bindings of booleans, and sometimes bindings of optionals.
Well, our SwiftUINavigation library tries to fill the gap, with the help of our CasePaths library, by providing view modifiers that allow you to drive navigation from the Destination
enum:
.alert(
$model.destination,
case: /MeetingDetailModel.Destination.alert
) { action in
await model.alertButtonTapped(action)
}
.navigationDestination(
unwrapping: $model.destination,
case: /MeetingDetailModel.Destination.record
) { $model in
RecordMeetingView(model: model)
}
.sheet(
unwrapping: $model.destination,
case: /MeetingDetailModel.Destination.edit
) { $model in
EditMeetingView(model: model)
}
These are custom view modifiers that ship with the SwiftUINavigation library that allow you to drive navigation from an optional enum value. You first specify a binding to the optional enum value, and then you specify a case path to isolate the case you care about for the navigation.
This works incredibly well, but it also a bit verbose.
Thanks to the new @CasePathable
macro provided by our CasePaths library, we can greatly simplify the above view modifiers. We can start by annotating the Destination
enum with the macro:
@CasePathable
enum Destination {
case alert(AlertState<AlertAction>)
case edit(EditMeetingModel)
case record(RecordMeetingModel)
}
Just that one line of additional code gives us the ability to perform dot-chaining syntax onto the $model.destination
binding for each case of the enum. This allows us to derive bindings that can be handed to the SwiftUI view modifiers, which massively simplifies the code we saw above:
-.alert(
- $model.destination,
- case: /MeetingDetailModel.Destination.alert
-) { action in
+.alert($model.destination.alert) { action in
await model.alertButtonTapped(action)
}
-.navigationDestination(
- unwrapping: $model.destination,
- case: /MeetingDetailModel.Destination.record
-) { $model in
+.navigationDestination(item: $model.destination.record) { model in
RecordMeetingView(model: model)
}
-.sheet(
- unwrapping: $model.destination,
- case: /MeetingDetailModel.Destination.edit
-) { $model in
+.sheet(item: $model.destination.edit) { model in
EditMeetingView(model: model)
}
There’s no need to deal with explicit case paths or the /
prefix operator for constructing case paths. It’s simpler and more fluent Swift code. We are even now using the vanilla SwiftUI view modifiers navigationDestination(item:)
and sheet(item:)
. There is no need for a custom view modifier anymore.
Update your dependency on SwiftUINavigation to version 1.1 today to start taking advantage of the new @CasePathable
macro, and more. Tomorrow we will discuss how these new case path tools have massively improved our Composable Architecture library.