EP 158 · Safer, Conciser Forms · Aug 30, 2021 ·Members

Video #158: Safer, Conciser Forms: Part 1

smart_display

Loading stream…

Video #158: Safer, Conciser Forms: Part 1

Episode: Video #158 Date: Aug 30, 2021 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep158-safer-conciser-forms-part-1

Episode thumbnail

Description

Previously we explored how SwiftUI makes building forms a snap, and we contrasted it with the boilerplate introduced by the Composable Architecture. We employed a number of advanced tools to close the gap, but we can do better! We’ll start by using a property wrapper to make things much safer than before.

Video

Cloudflare Stream video ID: aa8d48ecc5d6993ff58bb2fb7146a3f9 Local file: video_158_safer-conciser-forms-part-1.mp4 *(download with --video 158)*

References

Transcript

0:05

Earlier this year we devoted a series of episodes on the topic of “ concise forms .” In them we explored how SwiftUI’s property wrappers make it super easy to build form-based UIs, mostly because there is very little code involved in establishing two-way bindings between your application’s state and various UI controls.

0:24

We contrasted this with how the Composable Architecture handles forms. Because the library adopts what is known as a “unidirectional data flow”, the only way to update state is to introduce explicit user actions for each UI control and then send them into the store. Unfortunately, for simple forms, this means introducing a lot more boilerplate than what we see in the vanilla SwiftUI version.

0:48

But, then we demonstrated two really interesting things. First, we showed that some of that boilerplate wasn’t necessary. By employing some advanced techniques in Swift, such as key paths and type erasure, we were able to make the Composable Architecture version of the form nearly as concise as the vanilla SwiftUI version.

1:06

And then we showed that once you start layering on advanced behavior onto the form, such as side effects, the brevity of the vanilla Swift version starts to break down. You are forced to leave the nice world of using syntax sugar for deriving bindings, and instead need to do things like construct bindings from scratch. On the other hand, the Composable Architecture really shines when adding this behavior because it’s perfectly situated for handling side effects.

1:38

Even though we accomplished something really nice by the end of that series of episodes, there is still room for improvement. One problem with our current solution is that it’s not particularly safe. If you make use of the tools we built in those episodes you essentially open up your entire state to mutation from the outside. This goes against the grain of one of the core tenets of the Composable Architecture, which is that mutations to state are only performed in the reducer when an action is sent. We are going to show how to fix this deficiency.

2:08

Further, we will make the binding tools we developed last time even more concise. In fact, it will compete with vanilla SwiftUI on a line-by-line basis, and in some ways it can be even more concise than vanilla SwiftUI.

2:24

So, let’s start by giving a quick overview of what we accomplished last time. Concise forms recap

2:30

We’ve got the project open that we built in the last series of episodes, but with a few small changes that we will cover in a moment. Let’s first remind ourselves exactly what we created last time. If we run the application in the simulator we see a pretty simple form. It allows us to set a display name, toggle whether or not we want to protect our posts, another toggle for whether or not we want notifications, and a button to reset the form. The first two fields are pretty basic, you are allowed to interact with the UI control and it just immediately writes that value to the application’s state.

3:05

The notifications toggle is a lot more complicated. If we flip it on we are prompted with the iOS notifications alert so that we can either grant or deny permission. Notice that behind the alert we can see that the toggle is temporarily on and showing additional UI that appears only when it notifications are on. If we deny, then the toggle is automatically turned off, and if we try turning it back on we are immediately shown an error alert. If we grant permission then additional UI appears that allows us to customize where we want notifications, such as email versus mobile, and we can opt into periodically receiving a top posts digest.

4:01

In the series of episodes on concise forms we essentially built this screen three times. Once using vanilla SwiftUI, then with the Composable Architecture, and then again with the Composable Architecture but employing some new tools to reduce the boilerplate.

4:21

The vanilla SwiftUI version is mostly straightforward, outside of the effectful notifications work we have to do. It starts with a view model which holds a @Published field for each UI control on the screen: class SettingsViewModel: ObservableObject { @Published var alert: AlertState? @Published var digest = Digest.off @Published var displayName = "" { didSet { guard self.displayName.count > 16 else { return } self.displayName = String(self.displayName.prefix(16)) } } @Published var protectMyPosts = false @Published var sendNotifications = false @Published var sendMobileNotifications = false @Published var sendEmailNotifications = false … }

4:33

Only thing interesting in this code is that we are performing some extra logic to limit the number of characters in the text field. We have to be careful to not introduce an infinite loop, which can happen if you implement a didSet callback and mutate the field inside, and so that’s why we have the guard statement first.

5:10

Next in the view model we have a rather lengthy method that is called when the notifications toggle is changed: func attemptToggleSendNotifications(isOn: Bool) { guard isOn else { self.sendNotifications = false return } UNUserNotificationCenter.current() .getNotificationSettings { settings in guard settings.authorizationStatus != .denied else { DispatchQueue.main.async { self.alert = .init( title: "You need to enable permissions from iOS settings" ) } return } DispatchQueue.main.async { self.sendNotifications = true } UNUserNotificationCenter.current() .requestAuthorization(options: .alert) { granted, error in if !granted || error != nil { DispatchQueue.main.async { self.sendNotifications = false } } else { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } } } }

5:25

This does quite a bit:

5:27

First, if we are turning off the toggle then we let that go through immediately.

5:39

Next we load the user’s current iOS notification settings, an operation which is asynchronous.

5:50

Once the settings are received, if they previously denied us access then we can early out and show an error alert.

6:02

Otherwise we optimistically turn the toggle on and simultaneously fire off another asynchronous request to ask the user for notification permissions.

6:21

If that is denied we turn the toggle back off, and if it is granted we make a request to register for remote notifications, which is the API that can eventually give us the actually push token information.

6:41

Phew, so that’s a lot. As we can see from this code the more asynchronous work we perform the more our indentation is increasing. It would be interesting to see what this code looks like if we utilized Swift’s new async/await features rather than completion callbacks, but we’ll save that as an exercise for the viewer.

7:03

Finally, in the view model we have a method that can reset the state of the form: func reset() { self.digest = .off self.displayName = "" self.protectMyPosts = false self.sendNotifications = false }

7:09

So, the view model is a little intense, but for good reason. We are doing some complex logic inside in order to call multiple asynchronous APIs.

7:18

Fortunately, the view is a lot simpler. struct VanillaSwiftUIFormView: View { @ObservedObject var viewModel: SettingsViewModel var body: some View { Form { Section(header: Text("Profile")) { TextField("Display name", text: $viewModel.displayName) Toggle("Protect my posts", isOn: $viewModel.protectMyPosts) } Section(header: Text("Communication")) { Toggle( "Send notifications", isOn: Binding( get: { self.viewModel.sendNotifications }, set: { isOn in self.viewModel.attemptToggleSendNotifications(isOn: isOn) } ) ) if self.viewModel.sendNotifications { Toggle("Mobile", isOn: $viewModel.sendMobileNotifications) Toggle("Email", isOn: $viewModel.sendEmailNotifications) Picker( "Top posts digest", selection: $viewModel.digest ) { ForEach(Digest.allCases, id: \.self) { digest in Text(digest.rawValue) } } } } Button("Reset") { self.viewModel.reset() } } .alert(item: $viewModel.alert) { alert in Alert(title: Text(alert.title)) } .navigationTitle("Settings") } }

7:21

For the most part it’s pretty straightforward. We’re just constructing a hierarchy of views, some of which are UI controls that take bindings. The real special sauce of SwiftUI is how it can construct two-way bindings from an observable object, basically for free. In fact, there are 6 places we are taking advantage of this: $viewModel.displayName … $viewModel.protectMyPosts … $viewModel.sendMobileNotifications … $viewModel.sendEmailNotifications … $viewModel.digest … $viewModel.alert

7:58

This is awesome, and it’s what allows this view to be so short and succinct.

8:03

However, there is one place where we have to slightly deviate from the status quo. We unfortunately cannot directly derive a binding for the notifications toggle because that would give the UI control instant write access to that state.

8:18

We don’t want to write to it the moment you tap the toggle. Instead we want to do a bit of extra work to load notification settings in order to figure out if you’ve previously denied permission so that we can completely skip enabling the toggle and just show an error alert instead.

8:39

This use case is somewhat out of the “happy-path” purview that SwiftUI makes so nice. Instead of deriving a binding automatically, we have to construct one from scratch so that in the set argument we can hit a view model endpoint instead of just thoughtlessly mutating the state immediately: Toggle( "Send notifications", isOn: Binding( get: { self.viewModel.sendNotifications }, set: { isOn in self.viewModel.attemptToggleSendNotifications(isOn: isOn) } ) )

9:03

This is unfortunate, but it’s the way it has to be. If you need to bypass the binding setter so that you can layer on some more complicated logic, you really have no choice but to construct a binding from scratch.

9:15

But all-in-all, this form is still quite concise. It’s worth repeating how incredible it is that one can create a view model with a few @Published fields, hook up that view model to a view, and everything just works.

9:27

So, after we built this form in vanilla SwiftUI we turned to the Composable Architecture. And let’s just say it was not so concise. Modeling the state was mostly the same, except we got to use a simple value type rather than a class: struct InconciseSettingsState: Equatable { var alert: AlertState? = nil var digest = Digest.daily var displayName = "" var protectMyPosts = false var sendNotifications = false }

9:50

But modeling the actions required specifying how every single UI control wants to change its state: enum InconciseSettingsAction: Equatable { case authorizationResponse(Result<Bool, NSError>) case digestChanged(Digest) case dismissAlert case displayNameChanged(String) case notificationSettingsResponse(UserNotificationsClient.Settings) case protectMyPostsChanged(Bool) case resetButtonTapped case sendNotificationsChanged(Bool) }

10:11

That is intense. But it’s not as bad as what the reducer looks like. Not only is the reducer quite long, but it’s also filled with simplistic action handling where we simply bind a value from the action case and then plug that value right into the state: case let .digestChanged(digest): state.digest = digest return .none case .dismissAlert: state.alert = nil return .none case let .displayNameChanged(displayName): state.displayName = String(displayName.prefix(16)) return .none case let .protectMyPostsChanged(protectMyPosts): state.protectMyPosts = protectMyPosts return .none case let .sendEmailNotificationsChanged(isOn): state.sendEmailNotifications = true return .none case let .sendMobileNotificationsChanged(isOn): state.sendMobileNotifications = isOn return .none

10:42

The view looks similar to what we did in the vanilla SwiftUI version, except in order to construct a binding we must invoke the .binding method on the view store in order to specify which state we want to bind to and which action should be sent when the binding is written to: TextField( "Display name", text: viewStore.binding( get: \.displayName, send: InconciseSettingsAction.displayNameChanged ) )

11:09

All of this verboseness adds up quite a bit, making the Composable Architecture version 60% longer than the vanilla SwiftUI version.

11:19

The only saving grace of the Composable Architecture version is that due to how the library typically encourages us to properly design and control our dependencies, and so the application we got on the other side was very testable. We even wrote an extensive test suite that exercised many nuanced flows of how the user can grant or deny notification permissions.

11:39

Also, a line-by-line comparison between these applications isn’t totally fair. The Composable Architecture is definitely longer, but as we said, it’s also built in a way that makes it possible test the entire feature. If the same attention had be given to the vanilla SwiftUI version it would have definitely gotten longer too.

11:56

But, that doesn’t mean there isn’t room for improvement. By leveraging some advanced features of Swift, such as key paths and type erasure, we were able to eliminate all of the actions that correspond to a UI control wanting to write to a simple binding, and all of the reducer cases that simply bind to a case’s value just to then go and update state.

12:24

The actions are replaced by what is known as a BindingAction : enum SettingsAction: Equatable { case authorizationResponse(Result<Bool, NSError>) case binding(BindingAction<SettingsState>) case notificationSettingsResponse(UserNotificationsClient.Settings) case resetButtonTapped }

12:34

Now the SettingsAction enum only contains non-UI binding actions, such as when the reset button is tapped, or when effects feed their actions back into the system.

12:46

The reducer also has a lot of code removed. Basically anywhere we were simply binding to a case just to update a single field on the state. Once those actions were removed from the reducer implementation we just need to invoke the .binding higher-order reducer, which forces you to specify how to optionally extract the binding action from a settings action: let settingsReducer = Reducer< SettingsState, SettingsAction, SettingsEnvironment > { state, action, environment in … } .binding(action: /SettingsAction.binding)

13:08

This reducer method automatically takes care of all the boilerplate-y stuff that we were doing in the reducer previously, like extracting the value from a case just to assign a value in the state. It allows us to reduce quite a bit of boilerplate because we are are now free to add as many UI controls to our domain and the size of the action enum and reducer will not grow at all.

13:27

The view mostly looks the same as in the previous Composable Architecture version, except there is a special overload of the .binding method specifically for sending binding actions: TextField( "Display name", text: viewStore.binding( keyPath: \.displayName, send: SettingsAction.binding ) )

13:44

So, the BindingAction and binding helpers that we developed last time help reduce quite a bit of boilerplate. And the best part is that it scales well in the sense that if we needed to add more and more UI controls to this screen it would not cause our code to bloat.

14:05

Let’s quickly remind ourselves how those helpers work since we will soon be deep in the internals. The BindingAction type is supposed to be a wrapper around a key path and a value that represents how you want mutate a piece of the state. However, due to some limitations of generics in Swift we are forced to do some trickery with type erasure in order to make sure that BindingAction is equatable: struct BindingAction<Root>: Equatable { let keyPath: PartialKeyPath<Root> let setter: (inout Root) -> Void let value: Any let valueIsEqualTo: (Any) -> Bool init<Value>( _ keyPath: WritableKeyPath<Root, Value>, _ value: Value ) where Value: Equatable { self.keyPath = keyPath self.value = value self.setter = { $0[keyPath: keyPath] = value } self.valueIsEqualTo = { $0 as? Value == value } } static func set<Value>( _ keyPath: WritableKeyPath<Root, Value>, _ value: Value ) -> Self where Value: Equatable { .init(keyPath, value) } static func == (lhs: Self, rhs: Self) -> Bool { lhs.keyPath == rhs.keyPath && lhs.valueIsEqualTo(rhs.value) } }

15:24

But, with that type defined it’s pretty straightforward to implement the .binding higher-order reducer: extension Reducer { func binding( action bindingAction: CasePath<Action, BindingAction<State>> ) -> Self { Self { state, action, environment in guard let bindingAction = bindingAction.extract(from: action) else { return self.run(&state, action, environment) } bindingAction.setter(&state) return self.run(&state, action, environment) } } }

15:41

This enhances the current reducer, which is represented by self , by first trying to extract a binding action from any action that comes into the system. If that fails we just run the current reducer like normal. However, if it succeeds we can use the binding action to mutate the state with the key path and value it holds, and then run the current reducer like normal. These few lines of code is what allows us to delete all of the repetitive work in the reducer of us binding to a case’s value just to turn around and stick that value into state.

16:20

Next we have the overloaded .binding method on ViewStore that leverages binding actions to do its work instead of needing to specify a separate case for every single UI component that wants to mutate a piece of state: extension ViewStore { func binding<Value>( keyPath: WritableKeyPath<State, Value>, send action: @escaping (BindingAction<State>) -> Action ) -> Binding<Value> where Value: Equatable { self.binding( get: { $0[keyPath: keyPath] }, send: { action(.set(keyPath, $0)) } ) } }

16:40

And then finally we had this funny little pattern matching operator: func ~= <Root, Value> ( keyPath: WritableKeyPath<Root, Value>, bindingAction: BindingAction<Root> ) -> Bool { bindingAction.keyPath == keyPath }

16:48

This operator is special in Swift. It allows you to tap into pattern matching capabilities, such as when you switch on a value or use if case let syntax. It allows us to destructure a binding action to figure out which field is being mutated. For example, we wanted to detect when the displayName field is changed so that we could truncate it to a max of 16 characters: case .binding(\.displayName): state.displayName = String(state.displayName.prefix(16)) return .none

17:22

We also detect when the notifications toggle is changed so that we can request notification settings: case .binding(\.sendNotifications): guard state.sendNotifications else { return .none } state.sendNotifications = false return environment.userNotifications .getNotificationSettings() .receive(on: environment.mainQueue) .map(SettingsAction.notificationSettingsResponse) .eraseToEffect()

17:34

So, that is everything we accomplished in our series of episodes on concise forms. We just did a speed run through 3 episodes worth of content, so if you want to take a deeper dive into these topics, especially the type erasure stuff, then we suggest you go watch those episodes. Making concise forms safer

17:50

But now we’re ready to start improving our tools for concise forms. We’re going to start with safety. It turns out that by employing binding actions in our feature we are opening up the flood gates with respect to allowing anyone to perform any mutation they want.

18:11

For example, suppose there was some operation in this screen that takes time to process, such as if we needed to submit our settings to a server. We may want a loading indicator on the screen to let the user know what is going on, and so we could model this with an isLoading boolean in our state: struct SettingsState: Equatable { … var isLoading = false … }

18:32

This field is different from the rest in that its value is not editable from the view directly. There is no UI control for which you should be allowed to flip this boolean to true or false. Instead it gets mutated internally in the reducer when certain actions are sent. For example, a button may be tapped to start an effect, which means the boolean gets flipped to true, and then later the effect feeds a response back into the system and so the boolean flips back to false.

18:58

However, by employing binding actions on this feature we are getting full, unfettered access for the view to mutate any part of the state they want, at any time. From the perspective of the reducer, we will be none the wiser. For example, when the reset button is tapped in the UI we could do something really strange like flip isLoading to true: Button("Reset") { viewStore.send(.binding(.set(\.isLoading, true))) viewStore.send(.resetButtonTapped) }

19:28

This would be really strange. Not only does it allow our views to do things it shouldn’t be doing, but worse, it allows our views to take on more logic than is necessary. Ideally our views are completely logicless. The only thing they should be doing is constructing view hierarchy and sending actions to the store. But, by allowing this mutation access from anywhere we may be tempted to hack in a few mutations here and there to get the job done, when really that work should all be in the reducer.

19:54

What if instead of allowing the wholesale mutation of state via a binding action, we could mark which fields we want to be bindable and all the other fields will be unchangeable via binding actions. Then we could protect the isLoading field from being mutated from the outside while still allowing us to derive bindings for every other field.

20:13

Swift has a feature that is perfect for marking some struct fields with extra data to layer on additional functionality: property wrappers. They allow you to annotate any field with some information while still allowing the struct to mostly behave as if the field were a plain old data type. It’s great for hiding away those details until they are actually needed.

20:33

So, what if we had a property wrapper that allowed us to annotate which fields are allowed to be used with binding actions: struct SettingsState: Equatable { @BindableState var alert: AlertState? = nil @BindableState var digest = Digest.daily @BindableState var displayName = "" var isLoading = false @BindableState var protectMyPosts = false @BindableState var sendNotifications = false @BindableState var sendMobileNotifications = false @BindableState var sendEmailNotifications = false }

21:01

Most importantly, isLoading is not marked as bindable, and so ideally this should prevent that field from being mutated via a binding action.

21:09

Creating this property wrapper is easy enough. We just create a new type with the BindableState name, make it generic over the type of data it wraps, and mark the type with the @propertyWrapper annotation: @propertyWrapper struct BindableState<Value> { var wrappedValue: Value }

21:36

We now get an error in our SettingsState struct: Type ‘SettingsState’ does not conform to protocol ’Equatable’

21:45

But this is just because we need to make BindableState equatable when its wrapped value is equatable with a conditional conformance: extension BindableState: Equatable where Value: Equatable {}

22:05

And now everything builds again.

22:08

Next we’ll update all of the binding tools we covered just a moment ago to work only with BindableState . This will prevent the tools from working with any fields that have not be marked with the property wrapper.

22:19

The place to do this is the static set method, which is the only public interface for constructing a binding action. We will force this method to go through a BindableState value, rather than just any value: struct BindingAction<Root>: Equatable { … static func set<Value>( _ keyPath: WritableKeyPath<Root, BindableState<Value>>, _ value: Value ) -> Self where Value: Equatable { .init(keyPath.appending(path: \.wrappedValue), value) } }

22:54

This should prevent us from ever constructing an action that can mutate a piece of state that isn’t marked as bindable.

23:03

We want to do something similar for the method on ViewStore that derives bindings. Again we will require that we go through some BindableState rather than any piece of state: extension ViewStore { func binding<Value>( keyPath: WritableKeyPath<State, BindableState<Value>>, send action: @escaping (BindingAction<State>) -> Action ) -> Binding<Value> where Value: Equatable { self.binding( get: { $0[keyPath: keyPath].wrappedValue }, send: { action(.set(keyPath, $0)) } ) } }

23:32

And that’s all the changes we need to make to the existing binding tools work with BindableState .

23:38

Unfortunately, the application is not compiling. We get a bunch of errors in the spots we try to derive bindings from the view store: TextField( "Display name", text: viewStore.binding( keyPath: \.displayName, send: SettingsAction.binding ) ) Key path value type ‘String’ cannot be converted to contextual type ‘BindableState<String>’

23:50

This is happening because we are now trying to pass along a key path to a string to something that expects a key path to a BindableState .

23:59

Right now we only have access to the plain string field representing displayName , but we somehow need access to the underlying BindableState . We know its around somewhere by virtue of the fact that we are using a property wrapper. But where’s it hiding?

24:14

Well, currently it’s only a private implementation detail of our SettingsState type. We can verify this opening up a function in the SettingsState scope and seeing that we have access to a whole bunch of new underscored properties: struct SettingsState: Equatable { … func f() { self._displayName as BindableState<String> } }

24:40

This shows that we have full access to all bindable fields, but only privately in the scope of the SettingsState in this file. By default, property wrappers only expose the wrapping type privately, which is why each of these properties has an underscore prefix.

24:53

It is possible to expose the wrapping type publicly by using what is known as the projectedValue property. If you implement this property, then you can magically access the actual wrapping type from the outside: @propertyWrapper struct BindableState<Value> { var wrappedValue: Value var projectedValue: Self { self } }

25:15

The projected value doesn’t even need to be of type Self . You can return any kind of value here, for example, in SwiftUI the @ObservedObject property wrapper returns an inner wrapper type for the projected value, which is the thing responsible for deriving bindings to its fields.

25:32

Just with that one line we now get access to a $ prefixed property that gives us access to the wrapping type just like the underscored property did, except this time its now a public property: func f() { _displayName as BindableState<String> $displayName as BindableState<String> }

25:54

So, now that we have access to the actual BindableState type that wraps around display name string, we would hope we could pass that along to view store binding helper: TextField( "Display name", text: viewStore.binding( keyPath: \.$displayName, send: SettingsAction.binding ) ) Key path value type ‘WritableKeyPath<SettingsState, BindableState<String>>’ cannot be converted to contextual type ‘KeyPath<SettingsState, BindableState<String>>’

26:11

However, we cannot. The binding method on ViewStore needs a WritableKeyPath to do its job, but it seems that \.$displayName only gives a regular KeyPath .

26:28

Well, the fix is easy enough. We just need to implement both a getter and setter for the projectedValue : @propertyWrapper struct BindableState<Value> { var wrappedValue: Value var projectedValue: Self { get { self } set { self = newValue } } }

26:45

This gives us read and write access to the BindableState wrapping type, which is what we need to provide a writable key path so that we can mutate the value wrapped inside.

26:54

With that change the code that derives a binding for displayName from the view store is now compiling: TextField( "Display name", text: viewStore.binding( keyPath: \.$displayName, send: SettingsAction.binding ) )

27:02

Let’s update all the other bindings in the view too.

27:19

Everything is now compiling except for one thing: viewStore.send(.binding(.set(\.isLoading, true))) Key path value type ‘Bool’ cannot be converted to contextual type ’BindableState<Bool>’

27:27

This is exactly what we want. We are no longer allowed to send binding actions into the store that would mutate fields that are not explicitly marked as @BindableState . The .set static method simply does not accept anything that is not annotated with the property wrapper. So this will never compile, and we can comment out this line. Next time: eliminating more boilerplate

27:59

So, everything works exactly as it did before, but we are now allowed to opt into which fields should be bindable. This makes our domain modeling much safer by allowing us to restrict the ways certain state is allowed to be mutated.

28:14

With this in place we are now in a position to reduce even more boilerplate in our binding tools. We’ve already made great strides by not forcing us to create an action for every single piece of state that wants to be mutated via a UI binding, but we can go further.

28:31

Right now constructing a binding to a piece of state is a bit of a pain. We have to invoke the binding method on ViewStore , which requires passing the key path as well as the enum case of our actions that identifies where the binding action resides. Usually this can all fit on a single line, albeit a pretty intense line, but we personally like to mix in copious amounts of newlines to break up our code and so for us this binding helper makes our code quite a bit longer.

28:59

Well, luckily there’s a better way. This idea was first brought to our attention by one of our viewers, June Bash , and then over time independently by a bunch of other viewers on Twitter and in our library’s GitHub discussions . Coupling their ideas with the work we have just done on the @BindableState wrapper we can shorten our code tremendously, and even restore some of the concise ergonomics that we know and love from vanilla SwiftUI…next time! References Collection: Composable Architecture Brandon Williams & Stephen Celis Note Architecture is a tough problem and there’s no shortage of articles, videos and open source projects attempting to solve the problem once and for all. In this collection we systematically develop an architecture from first principles, with an eye on building something that is composable, modular, testable, and more. https://www.pointfree.co/collections/composable-architecture Downloads Sample code 0158-safer-conciser-forms-pt1 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .