EP 161 · SwiftUI Navigation · Sep 27, 2021 ·Members

Video #161: SwiftUI Navigation: Tabs & Alerts, Part 2

smart_display

Loading stream…

Video #161: SwiftUI Navigation: Tabs & Alerts, Part 2

Episode: Video #161 Date: Sep 27, 2021 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep161-swiftui-navigation-tabs-alerts-part-2

Episode thumbnail

Description

We continue our journey exploring navigation with an examination of alerts and action sheets. We’ll compare their original APIs in SwiftUI to the ones that replace them in the SDK that just shipped, and do a domain modeling exercise to recover what was lost.

Video

Cloudflare Stream video ID: 84091da7a802d94ead2cc2fbb8f995a1 Local file: video_161_swiftui-navigation-tabs-alerts-part-2.mp4 *(download with --video 161)*

References

Transcript

0:05

So this is great, our alert can now be more dynamic based on what is happening in the view.

0:15

But there’s a few things that are not ideal about how we have modeled this problem.

0:21

First of all, holding both a boolean and an optional to represent an alert leaves us open to holding invalid states. For example, what if the boolean is true yet the optional is nil ? That means we are trying to present the alert but don’t have any data to actually show. Or if the boolean is false yet the optional is non- nil . That means we have some data we want to put into an alert, but we aren’t showing an alert.

0:42

This improperly modeled domain will add more and more complexity into your view because you can never really trust the data. You’ll always worry about the invalid states creeping up, causing you to sprinkle little bits of logic to handle them, and you’ll have to do extra work to clean up the state so that it stays as correct as possible. In fact, right now we are never nil -ing out the itemToDelete field, and so if another part of our view checks if its nil for some other part of the feature’s logic we run the risk of making decisions based on malformed data.

1:15

So, using the simple binding boolean API for alerts works great for alerts that are static, but it’s not the way to go for alerts that are dynamic. Luckily SwiftUI provides another version of the .alert modifier, so let’s take a look at that. Optional alert state

1:30

This overload of .alert takes a binding of an optional value: func alert<Item>( item: Binding<Item?>, content: (Item) -> Alert ) -> some View

1:47

This unifies the concept of showing and hiding the alert with the concept of providing data to customize the presentation of the alert. The optionality of the item in the binding represents if the alert should be shown or hidden, and the non- nil value produced represents the data needed to customize the alert. It’s a much better way of modeling the problem compared to holding a boolean and item separately.

2:11

So, let’s try it out. We might hope that we can simply drop the boolean and use just the optional itemToDelete : struct InventoryView: View { @ObservedObject var viewModel: InventoryViewModel // @State var deleteItemAlertIsPresented = false @State var itemToDelete: Item? … }

2:17

The action closure for the delete button becomes much simpler. We just need to track the itemToDelete , no need to toggle the boolean anymore: Button(action: { self.itemToDelete = item }) { Image(systemName: "trash.fill") }

2:25

And then we can derive a binding for the optional itemToDelete , which will invoke its closure when the optional flips to something non- nil and hands the closure that unwrapped value: .alert(item: self.$itemToDelete) { item in Alert( title: Text(item.name), message: Text("Are you sure you want to delete this item?"), primaryButton: .destructive(Text("Delete")) { self.viewModel.delete(item: item) }, secondaryButton: .cancel() ) }

3:04

And just like that the preview works exactly as it did before. Tapping the delete button brings up an alert with the item’s name in the title.

3:13

That worked out surprisingly well! We got the unwrapped value in the closure, and so it is very easy to access its name field and pass it to the viewModel.delete endpoint without dealing with messy optionals.

3:25

It’s worth noting that this overload of the .alert method requires that the item passed in conform to the Identifiable protocol, which is what SwiftUI uses to determine if the alert needs to be dismissed and re-shown when the item changes. This allows one to run many different alerts off of the same binding. For example, you could model all possible alerts on the screen as an enum, and then switch on that enum in the alert’s view builder closure to configure how the alert displays. This is particularly important due to a bug in SwiftUI which prevents you from chaining on multiple .alert view modifiers on a single view. Only the last chained .alert actually works.

4:06

Luckily for us our Item model already conforms to Identifiable since we wanted to use it in the ForEach view, which also requires the conformance.

4:26

It’s also worth noting that SwiftUI will now do the clean up for us by writing nil to the binding when the alert is dismissed. Previously, it was our responsibility to remember to nil the associated state out when dismissing the alert.

4:51

Things are looking good, but there’s still something not quite ideal about how our code is structured right now. We have to linearly scan this array to find the item we are deleting. Further, it will scan the entire array even if the item we are deleting is at the front of the collection. We could do more work to mitigate this, but it’s still not a great idea to search an array of an item to remove when we know there is at most one instance of that item.

5:25

We may be tempted to work with array indices instead of items. To do this we can zip the inventory collection with its indices: ForEach( zip(viewModel.inventory.indices, viewModel.inventory) ) { index, item in Type ‘(Int, Array<Item>.Element)’ cannot conform to ‘Identifiable’

5:47

This doesn’t build because the zipped element is a tuple and no longer conforms to Identifiable , but we can use a key path to specify how we should identify each row: ForEach( zip(viewModel.inventory.indices, viewModel.inventory), id: \.1.id ) { index, item in Generic struct ‘ForEach’ requires that ‘Zip2Sequence<Range<Array<Item>.Index>, [Item]>’ conform to ‘RandomAccessCollection’

6:10

But even that isn’t enough because ForEach requires that the data it is handed is a random access collection, and sadly zip returns a sequence that does not conform. A way around that would be to wrap the call in an array: ForEach( Array( zip(self.viewModel.inventory.indices, self.viewModel.inventory) ), id: \.1.id ) { index, item in

6:34

One bummer with doing the above is that we are creating a whole new array in order to get these offsets. There’s a proposal to rectify that and so in a future Swift we may not need to create this additional array.

6:58

But, now that we have an index at our disposal we would want to track that information when we show the alert: Button(action: { self.itemToDelete = (index, item) }) {

7:14

This doesn’t compile, but let’s push forward to see how things work out without fixing it.

7:21

Now that we have access to index when the alert shows we can adapt the .alert invocation to pass it to the view model: .alert(item: self.$itemToDelete) { index, item in Alert( title: Text(item.name), message: Text("Are you sure you want to delete this item?"), primaryButton: .destructive(Text("Delete")) { self.viewModel.delete(at: index) }, secondaryButton: .cancel() ) }

7:36

And now the view model method becomes very simple to implement: func delete(at index: Int) { withAnimation { // self.inventory.removeAll(where: { $0.id == item.id }) self.inventory.remove(at: index) } }

7:50

However, this too is problematic. An index is not a stable identifier for an item in the array. While the alert is presented the view model could be doing some asynchronous work that causes the items to be shuffled around or removed, and that can result in us removing the wrong item or even crashing.

8:22

Luckily there’s a better way, and it’s using the new Identified Collections library that we open sourced a few months ago. The library naturally grew out of our work on the Composable Architecture, but it has important use cases for any SwiftUI application.

8:37

Let’s first back out of these changes because we are not going to use array indices.

8:45

And let’s import the IdentifiedCollections library, which is super easy now thanks to Xcode’s integration with the Swift package index: import IdentifiedCollections

9:16

Then, instead of using a plain array of items we will use an IdentifiedArray of items: class InventoryViewModel: ObservableObject { @Published var inventory: IdentifiedArrayOf<Item> init(inventory: IdentifiedArrayOf<Item> = []) { self.inventory = inventory } … }

9:30

Everything still compiles even though we’ve made a pretty substantial change to our model, and that’s because IdentifiedArray s have a lot of the same features as regular arrays. But they also have new features, such as a .remove method that removes a particular element from the collection more easily: func delete(item: Item) { withAnimation { _ = self.inventory.remove(id: item.id) // self.inventory.removeAll(where: { $0.id == item.id }) } }

10:09

This will correctly find the single item corresponding to the id passed into .delete and remove just that item. We never have to worry about indices getting shuffled around, or needing to scan the entire array for the element.

10:22

If we run the preview again we will see that everything works just as before.

10:31

So, everything is seeming good, but let’s take a look at what kind of deep linking capabilities we have. When we introduced the tab view we moved its binding to a view model so that we could easily start up the application with a particular tab selected. We don’t have this ability with the alert. We can’t start the app with the inventory tab selected and the deletion alert presented.

11:18

This is easy enough to do when constructing an InventoryView directly, for example in the previews: struct InventoryView_Previews: PreviewProvider { static var previews: some View { let keyboard = Item(name: "Keyboard", color: .blue, status: .inStock(quantity: 100)) InventoryView( viewModel: .init( inventory: [ keyboard, Item( name: "Charger", color: .yellow, status: .inStock(quantity: 20) ), Item( name: "Phone", color: .green, status: .outOfStock(isOnBackOrder: true) ), Item( name: "Headphones", color: .green, status: .outOfStock(isOnBackOrder: false) ), ] ), itemToDelete: keyboard ) } }

11:57

However, at the ContentView level, which is the root view that kicks off the entire application, we don’t have access to any of the @State variables that control the presentation of the alert: struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView( viewModel: .init( inventory: .init( inventory: [ Item( name: "Keyboard", color: .blue, status: .inStock(quantity: 100) ), Item( name: "Charger", color: .yellow, status: .inStock(quantity: 20) ), Item( name: "Phone", color: .green, status: .outOfStock(isOnBackOrder: true) ), Item( name: "Headphones", color: .green, status: .outOfStock(isOnBackOrder: false) ), ] ), selectedTab: .inventory ) ) } }

12:04

We could maybe thread that state through from the ContentView so that we can initially populate it, but it’s weird to make the ContentView hold onto state that it doesn’t actually care about. Also, by holding the alert state as an @State variable in the view we don’t have any way of testing its behavior.

12:42

In order to support deep linking to this particular state and to unlock testability of the state we just need to do what we’ve already done before: we need to move the state out of the view and into the view model.

12:47

So, let’s add a new @Published field to the view model and update the initializer: class InventoryViewModel: ObservableObject { @Published var inventory: IdentifiedArrayOf<Item> @Published var itemToDelete: Item? init( inventory: IdentifiedArrayOf<Item> = [], itemToDelete: Item? = nil ) { self.inventory = inventory self.itemToDelete = itemToDelete } … }

13:02

We can get rid of the @State from the view: struct InventoryView: View { @ObservedObject var viewModel: InventoryViewModel // @State var deleteItemAlertIsPresented = false // @State var itemToDelete: Item? … }

13:05

And we need to update the itemToDelete field in the view model when the delete button is tapped: Button(action: { self.viewModel.itemToDelete = item }) { … }

13:12

Or perhaps better would be to move that logic into a dedicated method on the view model: func deleteButtonTapped(item: Item) { self.itemToDelete = item } … Button(action: { self.viewModel.deleteButtonTapped(item: item) }) { Image(systemName: "trash.fill") }

13:39

And the alert will read from the view model rather than directly from the view’s state: .alert(item: self.$viewModel.itemToDelete) { item in … }

13:43

To get things in compiling order we just have to update the preview to pass along alert state directly to the view model rather than the view: InventoryView( viewModel: .init( inventory: [ keyboard, Item( name: "Charger", color: .yellow, status: .inStock(quantity: 20) ), Item( name: "Phone", color: .green, status: .outOfStock(isOnBackOrder: true) ), Item( name: "Headphones", color: .green, status: .outOfStock(isOnBackOrder: false) ), ], itemToDelete: keyboard ) )

13:53

And everything works just as it did before, but we now we have the ability to deep link even deeper into the application. For example, we can create a ContentView with a very specifically sculpted view model that switches the application to the inventory tab and brings up a deletion alert: struct ContentView_Previews: PreviewProvider { static var previews: some View { let keyboard = Item(name: "Keyboard", color: .blue, status: .inStock(quantity: 100)) ContentView( viewModel: .init( inventoryViewModel: .init( inventory: [ keyboard, Item( name: "Charger", color: .yellow, status: .inStock(quantity: 20) ), Item( name: "Phone", color: .green, status: .outOfStock(isOnBackOrder: true) ), Item( name: "Headphones", color: .green, status: .outOfStock(isOnBackOrder: false) ), ], itemToDelete: keyboard ), selectedTab: .inventory ) ) } }

14:24

That’s all it takes. It’s almost magic. By having a full representation of the tab and alert state in our view model it is trivial to open our application in whatever configuration we want. We are now seeing the very, very beginnings of what it means to deep link to a particular part of an application by just constructing the state that represents that navigation.

15:09

This also shows that although using local @State can help us get our feet wet really quickly with building features, it severely limits our ability to affect the view from the outside. If we want to be able to deep link into any state of our application we have no choice but to move all of that state to the view model and run the entire application from a single root view model, which itself can consist of many other child view models. New alert APIs and domain modeling

15:35

So, that’s how alerts work in SwiftUI…well, that is up until recently. With Xcode 13 and iOS 15 the alerts API got an update, and to be honest we’re a little perplexed by the changes. The new API embraces the version of domain modeling we dismissed as being too permissive by allowing invalid states. We’re not sure why these changes were made, but it seems like they may be here to stay, so let’s take a look.

16:05

The two overloads of the .alert view modifier we explored earlier are now deprecated, and in their place spring forth 14 new overloads 😬. It can be a little intense to see all of those overloads in autocomplete, but it really just boils down to these 2 main ones: func alert<A: View, M: View, T>( _ titleKey: LocalizedStringKey, isPresented: Binding<Bool>, presenting data: T?, actions: (T) -> A, message: (T) -> M ) -> some View

16:24

All the other ones are a variation on this one.

16:28

To invoke this method you must provide 5 arguments for an alert with a message. The first argument is the title of the alert, the second is a binding of a boolean to control when to show or dismiss the alert, the third is an optional piece of data that can be used to customize the actions and message of the alert, and the last two arguments are view closures that describe the buttons and message displayed in the alert.

16:44

That’s quite a bit!

16:46

Now, looking at this signature, something may stick out to you. We are providing both a binding of a boolean and an optional value. The binding controls whether the alert is shown or dismissed, and independently the optional is unwrapped and pass down to the view closures so that they can customize their logic.

17:10

This is the exact data modeling we rejected earlier in this episode when first exploring the alert APIs. We did this because it allows us to represent states that are invalid for our application. For example, what should we do if the boolean is true yet the optional is nil ? That means show an alert, but we don’t have any data to show. Or, what if the boolean is false yet the optional is non- nil ? That means we have data to show in an alert, yet the boolean that controls its appearance says not to display an alert.

17:40

These invalid states are what motivated us to unify the presentation of the alert with the state that drives the content of the alert by leveraging a single piece of optional state. That allows us to prevent invalid states at compile time rather than run time.

17:54

With this new API it is quite easy is to introduce run time warnings in our code, such as flipping the presentation boolean to true while not having any data to display: .alert( Text("Hi"), isPresented: .constant(true), presenting: Int?.none, actions: { int in Button("Hello \(int)") {} } )

19:03

This should be a compile time error, but it builds just fine, and already we’re seeing a problem, because our alert is displayed with a single “Cancel” button and not the actions that we specified for the data being presented. This makes sense because the data is nil , and so the actions closure cannot be evaluated, and instead it just quietly displays a cancel button instead.

19:25

This was a completely invalid state that the compiler would have previously caught for us, but now it’s relegated to the runtime.

19:32

In earlier betas, this invalid state was at the very least captured by Xcode’s runtime issue system, which could have helped us catch these bugs more easily, but since then those issues have disappeared, so it’s on us to notice the problem at runtime.

19:53

If that weren’t bad enough, it also seems that now the title of the alert must be provided statically at the time of constructing the view hierarchy whereas previously it could be dynamically determined at the time of presentation. This meant you could have further logic to customize the title of the alert depending on what triggered the alert. For example, currently we are using the item whose delete button was tapped in order to populate the title of the alert, but doing so with this new API is going to be more awkward.

20:18

So, this is sad to see, but alas it’s how the API is now designed. So, how can we use it?

20:24

Well, let’s try using the data we currently have available to us in the view model to fill in the arguments that the new .alert modifier expects. Right off the bat we are met with some friction because we are expected to statically provide a title immediately, but our alert works by using the name of the item that is being removed. This means we have to do some work to coalesce a nil value to a string: .alert( Text(self.viewModel.itemToDelete?.name ?? ""), … )

21:01

Already this seems a little awkward, but let’s keep moving on.

21:06

Next we have the isPresented argument, which is a binding of a boolean. We don’t have a boolean in our state, and we don’t want one because it muddies our domain modeling, but we do have an optional and could deriving a binding that simply checks if the optional is nil or not: isPresented: Binding( get: { self.viewModel.itemToDelete != nil }, set: { isPresented in if !isPresented { self.viewModel.itemToDelete = nil } } ),

21:47

Take note that in the set endpoint we detect if false is being written to the binding so that we can nil out the state, but if true is being written we do nothing.

21:57

Next we have the presenting argument, which is just the optional state that will be unwrapped and provided to the actions and message closures: presenting: self.viewModel.itemToDelete,

22:07

And finally we have the actions and message closures, which are handed an honest, unwrapped Item value, which can be used to customize those views: actions: { item in Button("Delete", role: .destructive) { self.viewModel.delete(item: item) } }, message: { _ in Text("Are you sure you want to delete this item?") }

22:42

We were at least able to omit the cancel button, which is included for us automatically.

22:53

And now, when we run the preview we’ll see that we can still deep link to an alert, even with this new API, but it isn’t pretty: .alert( Text(self.viewModel.itemToDelete?.name ?? ""), isPresented: Binding( get: { self.viewModel.itemToDelete != nil } set: { isPresented in if !isPresented { self.viewModel.itemToDelete = nil } } ), presenting: self.viewModel.itemToDelete, actions: { item in Button("Delete", role: .destructive) { self.viewModel.delete(item: item) } }, message: { _ in Text("Are you sure you want to delete this item?") } )

23:08

This is a lot of work to do to remove invalid states at compile time, and if we had to do it for every alert, it would quickly add up. Maybe instead we can cook up some helpers that hide away all of this messiness.

23:18

First, maybe we can extract the ad hoc binding into a binding transformation by defining it more generally on the Binding type.

23:26

Let’s create a “SwiftUIHelpers.swift” file to house general helpers like this. We can then get a signature in place. import SwiftUI extension Binding { func isPresent() -> Binding<Bool> { } }

23:26

This is a method that will transform a binding of a value into a binding of a boolean, but what determines whether or not that boolean is true or false is whether or not the value is present or nil , so we need to constrain this method to when the value is optional: import SwiftUI extension Binding { func isPresent<Wrapped>() -> Binding<Bool> where Value == Wrapped? { } }

24:02

And then in the body of the method we’ll return a brand new binding by doing the ad hoc we did before but more generally. import SwiftUI extension Binding { func isPresent<Wrapped>() -> Binding<Bool> where Value == Wrapped? { .init( get: { self.wrappedValue != nil }, set: { isPresented in if !isPresented { self.wrappedValue = nil } } ) } }

24:27

We can then use this helper when we call out to the new alert API: .alert( Text(self.viewModel.itemToDelete?.name ?? ""), isPresented: self.$viewModel.itemToDelete.isPresent(), presenting: self.viewModel.itemToDelete, actions: { item in Button("Delete", role: .destructive) { self.viewModel.delete(item: item) } }, message: { _ in Text("Are you sure you want to delete this item?") } )

24:44

This is already looking a little bit better, but it’s still a lot of noise and a lot of extra work done in line to deal with optionality.

24:52

Perhaps we could also define an overload of alert that handles these remaining issues. We will mimic the API that uses a binding of an optional, which we know offers us safety and conciseness, but under the hood it will call out to the new API. We will also allow the title to the customized by the unwrapped optional state, rather than forcing it to be provided statically at the call site: extension View { func alert<A: View, M: View, T>( title: (T) -> Text, presenting data: Binding<T?>, @ViewBuilder actions: @escaping (T) -> A, @ViewBuilder message: @escaping (T) -> M ) -> some View { self.alert( data.wrappedValue.map(title) ?? Text(""), isPresented: .init( get: { data.wrappedValue != nil }, set: { if !$0 { data.wrappedValue = nil } } ), presenting: data.wrappedValue, actions: actions, message: message ) } }

27:21

And to use this API we can just clean up our existing .alert usage by leveraging the binding to an optional Item : .alert( title: { Text($0.name) }, presenting: self.$viewModel.itemToDelete, actions: { item in Button("Delete", role: .destructive) { self.viewModel.delete(item: item) } }, message: { _ in Text("Are you sure you want to delete this item?") } )

27:50

This looks much better. Confirmation Dialog, neé Action Sheet

27:53

So, that’s how alerts work in SwiftUI, but there is another closely related concept and that’s action sheets. They are very similar to alerts, so we’re not going to go into great detail, but we still think it’s important for our viewers to know how they work.

28:05

The APIs for action sheets are almost identical to alerts. Up until Xcode 13 there were two overloads, one for displaying action sheets from a boolean binding, and another for displaying action sheets from a binding of an optional. Xcode 13 has revised the API in the same way as alerts. There are now 12 overloads, and they now take both a boolean binding and an optional piece of state. Further, Xcode 13 has renamed .actionSheet to .confirmationDialog , perhaps in order to prevent confusion with the .sheet API, which is responsible for displaying modals.

28:33

Let’s quickly change our current application to use a confirmation dialog instead of an alert. We’re going to have the same similar problems as we did with alerts since the API requires us to specify a boolean and an optional. However, we can cook up a helper for confirmation dialogs that looks almost exactly like what we did for alerts: extension View { func confirmationDialog<A: View, M: View, T>( title: (T) -> Text, titleVisibility: Visibility = .automatic, presenting data: Binding<T?>, @ViewBuilder actions: @escaping (T) -> A, @ViewBuilder message: @escaping (T) -> M ) -> some View { self.confirmationDialog( data.wrappedValue.map(title) ?? Text(""), isPresented: .init( get: { data.wrappedValue != nil }, set: { if !$0 { data.wrappedValue = nil } } ), titleVisibility: titleVisibility, presenting: data.wrappedValue, actions: actions, message: message ) } }

29:37

And if we swap out our app’s alert for confirmationDialog , it deep links into a confirmation dialog at the bottom of the screen, but notably missing its title, which appears to be the default behavior on an iPhone. To force the title to render we can change the title visibility: .confirmationDialog( title: { Text($0.name) }, titleVisibility: .visible, presenting: self.$viewModel.itemToDelete, actions: { item in Button("Delete", role: .destructive) { self.viewModel.delete(item: item) } }, message: { _ in Text("Are you sure you want to delete this item?") } )

30:12

And now it shows up! Next time: sheets

30:30

So, that’s the basics of tab views, alerts and confirmation dialogs in SwiftUI, and it’s all looking pretty promising. We are able to drive these very basic navigation interactions purely with state changes. We can flip a single field to change the selected tab at the root, or we can flip a field to be non- nil and instantly get an alert showing.

30:50

We are seeing the beginnings of what it means to model navigation in state as well as how one can instantly support deep linking by just constructing the piece of state that corresponds to the navigation, and just let SwiftUI do the rest.

31:03

But so far the “navigation” we have explored is quite simple. Tab views have all of their child views present at once and then a simple binding controls which tab is currently active. And alerts are presented and dismissed via a transient piece of optional state, but the alert itself has no real behavior. It’s just some data and a few buttons that can invoke some actions.

31:27

Navigation starts to get really complicated when things can nest so that you can navigate to one screen, and then from there navigate to another screen, and on and on, and each screen picks up behavior of its own. That’s when modeling navigation in state starts to really take the form of a tree.

31:44

To get our feet wet in exploring this we are going to start with sheets in SwiftUI, which are modal screens that slide up over the main content of the screen. Sheets sit somewhere between tab views and alerts on the spectrum of navigation. They are presented and dismissed with optional state like alerts, but they can also hold onto complex behavior of their own like tab views.

32:06

We need to figure out how we can spin up a new view model to hand to the sheet when it’s presented so that it can have behavior, and then tear down the view model when the sheet is dismissed.

32:18

That’s going to take some time to get right, so let’s jump in…next time! References SwiftUI Navigation Brandon Williams & Stephen Celis • Nov 16, 2021 After 9 episodes exploring SwiftUI navigation from the ground up, we open sourced a library with all new tools for making SwiftUI navigation simpler, more ergonomic and more precise. https://github.com/pointfreeco/swiftui-navigation Collection: Derived Behavior Brandon Williams & Stephen Celis • May 17, 2021 Note The ability to break down applications into small domains that are understandable in isolation is a universal problem, and yet there is no default story for doing so in SwiftUI. We explore the problem space and solutions, in both vanilla SwiftUI and the Composable Architecture. https://www.pointfree.co/collections/case-studies/derived-behavior Downloads Sample code 0161-navigation-pt2 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 .