EP 160 · SwiftUI Navigation · Sep 20, 2021 ·Members

Video #160: SwiftUI Navigation: Tabs & Alerts, Part 1

smart_display

Loading stream…

Video #160: SwiftUI Navigation: Tabs & Alerts, Part 1

Episode: Video #160 Date: Sep 20, 2021 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep160-swiftui-navigation-tabs-alerts-part-1

Episode thumbnail

Description

Navigation is a really, really complex topic, and it’s going to take us many episodes go deep into it. We will begin our journey by coming up with a precise definition of what “navigation” is, and by exploring a couple simpler forms of navigation.

Video

Cloudflare Stream video ID: 1a529704fb58f7c21dafa3b0cea9d4fd Local file: video_160_swiftui-navigation-tabs-alerts-part-1.mp4 *(download with --video 160)*

References

Transcript

0:05

Today we are going to begin a long series of episodes that will dive deep into navigation concepts from first principles. We’ve been working on this series off and on for nearly a year, and we’re finally ready to tackle it.

0:17

We will be using SwiftUI as the primary tool to uncover the true essence of what navigation really is, but the concepts we discover will be just as applicable to UIKit or really any UI application no matter what language, framework or operating system used.

0:31

Navigation is a really, really complex topic. It doesn’t have a concise definition, it permeates nearly every part of our applications, some might even say it infects our applications, and it really strains our ability to build pieces of an application in small, reusable and isolated components.

0:47

This initial series of episodes will be primarily focused on vanilla SwiftUI. We want to understand navigation from the perspective of what tools Apple gives us out of the box. Once we do complete this deep dive into SwiftUI navigation we will be in a much better position to develop tools for navigation in the Composable Architecture, which will be a future series of episodes. What is navigation?

1:06

Let’s start with the basics. We are going to try our best to pin down a somewhat usable definition for what exactly “navigation” is.

1:16

So, what is navigation?

1:19

Well, that’s a tough question and it’s hard to distill all of navigation down into a single sentence, but here’s our attempt. To us, navigation will mean some kind of mode change in the application. Perhaps the most prototypical example of this in the iOS world is that of a navigation drill down. A user taps on a button, and an animation occurs to signify that you are moving from the current screen over to a whole new screen.

1:41

But there are more examples. Modal sheets are also an example of navigation. A user can tap on a button to make a sheet come up over the screen to signify they are being taken to a new mode of the application, and has the added benefit that it feels a bit more temporary since you can do a simple swipe down to dismiss it. Popovers and full screen covers are other examples, as they are basically the same as sheets except they take up either a small portion of the screen or the full screen.

2:10

So those examples are probably not too controversial, but we are also going to include things like alerts and action sheets in the list of things we consider to be “navigation”. Again they are presented and dismissed with some user interaction, and they signify moving to a new mode of the application. We’ll even consider tab views as a form of navigation. After all, they hold multiple screens at once that the user can flip through.

2:36

But these aren’t the only types of navigation out there. It’s not just what Apple’s frameworks hand down to us. We are free to create our own mode changes in our applications. This could be anything from a little pop up that displays in the middle of the screen, a little status banner that appears from the top or bottom of the screen, or really just anything that can be presented and dismissed.

2:55

So navigation is quite a universal concept when it comes to applications, and if that wasn’t already intimidating enough, there’s two more complex facets we want to deeply explore.

3:06

First, these mode changes we are trying to describe form a tree-like structure. That is, once inside one mode you can spawn another mode, and then another mode, and so on. So you could drill down to some screen, and then from there present a sheet, and then inside that sheet drill down to a screen, and then an alert could be presented, and all of that could be wrapped inside a tab view. Once we see navigation as a simple tree structure we are then naturally led to representing navigation as just simple data, and then all types of cool things start to happen. Deep linking becomes a simple matter of constructing a leaf node of the tree, and then letting SwiftUI do the rest. And we get to test how navigation behaves by just asserting on simple data values.

3:50

Second, when we navigate from a parent screen to a new child screen we want that screen to be able to have a set of behavior all on its own. It should be able to perform asynchronous work, side effects, have complex logic, and more. And even be able to have the child and parent communicate with each other. Unfortunately, Apple doesn’t ship with all the tools necessary to derive new behaviors from parent domains to hand down to child domains. There are some tools, but we need more. Luckily we can fill that gap ourselves, and in fact this was a topic we went deep into in our 5-part series on “ derived behavior ”, and we will be going even deeper while exploring navigation.

4:28

That is a lot of talking about navigation with no actual code, so let’s get our hands dirty. We’re going to start by looking at what SwiftUI gives us out of the box for navigation so that we can better understand how these tools fit into this idea of “mode changing” that we are putting forth, and so that we can see what problems they solve and what problems remain to be solved. Tab views

4:47

Let’s get our feet wet by considering one of the simpler forms of navigation, which is a tab view.

4:53

I’ve got a fresh SwiftUI project opened up right here, and we can stick a tab view right at the root of the content view by opening up a TabView and providing the content we want for each tab: struct ContentView: View { var body: some View { TabView { Text("One") Text("Two") Text("Three") } } }

5:15

Now the preview is showing the text “One” right in the middle of the screen, and a tab bar at the bottom, but no bar items inside it. To have those appear we must mark each tab’s content view with its tab item view: struct ContentView: View { var body: some View { TabView { Text("One") .tabItem { Text("One") } Text("Two") .tabItem { Text("Two") } Text("Three") .tabItem { Text("Three") } } } }

5:38

Now already this is a functioning tab view. We can tap around on the tab items and we see the content in the middle switching.

5:52

So, we’ve already got some navigation in our application, but our modeling of the navigation isn’t as strong as it could be. Right now we have no representation of the current tab we are on in our code. There’s no way for us to know which tab is currently selected and there’s no way for us to programmatically set the selected tab. Right now navigation is just “fire and forget”. It just happens into the void without us knowing what is going on.

6:16

The initializer we are currently using to construct TabView looks like this: public init(@ViewBuilder content: () -> Content)

6:20

You just provide some views to put into the tabs and it takes care of the rest.

6:24

There’s an alternative initializer that takes a binding which can be used to figure out which tab is selected and to set the currently selected tab: public init( selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content )

6:35

This is a pretty common pattern in SwiftUI APIs. There will often be a very simple initializer that allows you to create a UI component in a “fire and forget” fashion, where you don’t need full representation of the component’s state in your own state. And then there will also be a more complicated initializer that allows you to fully take control over everything in the component. The more you make use of these more powerful initializers the better position you will be in for programmatic navigation and deep linking, amongst other benefits.

7:06

The easiest way to get access to a binding is to introduce some local @State to the view, which for now we can use an integer: struct ContentView: View { @State var selection = 1 … }

7:16

Then we can make the tab view use this binding in order to control the selected tab: var body: some View { TabView(selection: self.$selection) { … } }

7:26

And in order for the TabView to know which tab’s content is associated with which selection value we have to further tag each content view: TabView(selection: self.$selection) { Text("One") .tabItem { Text("One") } .tag(1) Text("Two") .tabItem { Text("Two") } .tag(2) Text("Three") .tabItem { Text("Three") } .tag(3) }

7:44

And now the application works exactly as it did before, but it has extra capabilities now. For one thing, we can now create this view for the preview with a different tab initially selected: struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(selection: 2) } }

8:13

This wasn’t possible to do until we took the extra time to model the tab’s selection explicitly in state. We are now starting to see some of the very beginning hints of what it means to “deep link” into an application. At any time we can affect what UI is being shown on the screen by just changing a little bit of state.

8:34

For example, suppose we wanted to introduce a button that changed which tab we are on. We could add this button to the first tab such that when it is tapped we flip ourselves to the second tab: VStack { Text("One") Button("Go to 2nd tab") { self.selection = 2 } } .tabItem { Text("One") } .tag(1)

8:52

And that works as expected.

9:01

However, it’s probably not a good idea to hold this kind of state as local @State inside a view. Being local means that it’s hard to make changes to it from outside this view, it’s also hard to make changes to the state from asynchronous work such as API requests, and it’s hard to test.

9:20

For example, while it’s true that we can create the ContentView set to any tab we want like this: ContentView(selection: 2)

9:29

We instantly lose that ability if ContentView is wrapped in another layer: struct ContainerView: View { var body: some View { ContentView(selection: 2) … } }

9:43

When we create an instance of ContainerView we forced to be pinned to the hard coded selection of 2 since that happens inside the body of the view. We have no access to that from the outside. We could of course thread that information through: struct ContainerView: View { let selectedTab: Int var body: { ContentView(selection: self.selectedTab) … } }

10:12

But it is really strange for the ContainerView to have to hold onto state it doesn’t care about just to pass it down to child views.

10:20

And if that wasn’t bad enough, passing along values to @State doesn’t necessarily work the way you expect. The initialization of @State is tied to the identity and lifecycle of the view that holds it, and so it is only instantiated one single time, and it can no longer be influenced from the outside. We’ll see why this can be a problem later on in this series.

10:49

So, let’s introduce an observable object to hold onto this state instead: class AppViewModel: ObservableObject { @Published var selectedTab: Int init(selectedTab: Int = 1) { self.selectedTab = selectedTab } }

11:11

And then we can hold onto the view model in the view rather than the local @State : struct ContentView: View { @ObservedObject var viewModel: AppViewModel … }

11:20

And we can derive a binding from the published property for the TabView : TabView(selection: self.$viewModel.selectedTab) { … }

11:31

And we can reach through the view model to update the selected tab: Button("Go to 2nd tab") { self.viewModel.selectedTab = 2 }

11:36

Finally we have to update the preview and app entrypoint to pass along a view model: ContentView(viewModel: .init())

11:59

And everything now works exactly as before, but changes to tab state can now be controlled outside the local view layer.

12:22

We’re currently modeling tab selection with an integer, which is a bit problematic. An integer doesn’t say a lot about what is being selected. For example, in order to know what tab is being selected here: self.viewModel.selectedTab = 2

12:39

You have to hunt down the corresponding tag : .tag(2)

12:51

Worse, you can write code that compiles just fine but is nonsensical, like selecting a tab that doesn’t exist: self.viewModel.selectedTab = 2341324

12:58

Which will quietly select the first tab instead.

13:01

Luckily, selection can be modeled with any hashable type, so we can describe tabs using an enum that has a case for each tab: enum Tab { case one, two, three }

13:15

And use this type as the tag, instead. First, in the view model: @Published var selectedTab: Tab init(selectedTab: Tab = .one) { self.selectedTab = selectedTab }

13:19

And already our view no longer compiles: self.viewModel.selectedTab = 2341324 Cannot assign value of type ‘Int’ to type ‘Tab’

13:25

We must now assign a specific Tab value instead, making it far less likely we’ll make a mistake. self.viewModel.selectedTab = .two

13:32

The view is now in compiling order, but it can’t possibly work because we’re still tagging each tab with an integer. So we need to tag each view with a Tab value instead: .tag(Tab.one) … .tag(Tab.two) … .tag(Tab.three)

13:54

And now if we wanted to start our preview off on the second tab, we could pass this value along to the view model. ContentView(viewModel: .init(selectedTab: .two))

14:04

So that’s all there is to tab views in SwiftUI. You can either use it as a fire-and-forget component where the user is free to interact with the tabs however they please but we will not have any representation of their actions in the state of the application. Or you can use it in a slightly more complicated way by providing a binding to the tab view so that you can represent the current tab selection in your state, which allows you to know what the current tab is at any moment, and you can switch the tab whenever you want. Alerts and Action Sheets

14:30

Let’s spice things up a bit now. The tab view got our feet wet, but it’s not a very inspiring form of navigation. There’s just a simple binding that controls which tab we are on. Let’s consider a slightly more complex form of navigation that uses bindings in a more interesting way.

14:46

We are going to demonstrate how SwiftUI allows us to display alerts and action sheets. Alerts are full screen take overs that display in the middle of the screen and give you a simple title, message, and buttons to interact with. Action sheets are similar, but they appear from the bottom of the screen.

15:02

To explore this API we are going to take some inspiration from a demo application that we built long ago in a series of episodes we titled “ composable bindings .” In those episodes we showed that SwiftUI gives us some powerful tools for deriving new bindings from existing bindings, such as dynamic member lookup that allows us to dot-chain onto a binding to get a binding for a particular field.

15:22

That’s great, but there are also some tools missing. For example, what if your binding holds an enum and you want to derive a binding for a particular case of the enum? We needed this exact concept when building an inventory UI that allowed you to edit an inventory item and its UI changed depending on if the item was in stock or sold out.

15:46

So, we’re going to recreate parts of that old inventory application in order to further explore the concepts of bindings and navigation.

15:56

Let’s start by getting some domain into place. We modeled the concept of an inventory Item as a struct that holds a name, color and status: import SwiftUI struct Item: Equatable, Identifiable { let id = UUID() var name: String var color: Color? var status: Status enum Status: Equatable { case inStock(quantity: Int) case outOfStock(isOnBackOrder: Bool) var isInStock: Bool { guard case .inStock = self else { return false } return true } } struct Color: Equatable, Hashable { var name: String var red: CGFloat = 0 var green: CGFloat = 0 var blue: CGFloat = 0 static var defaults: [Self] = [ .red, .green, .blue, .black, .yellow, .white, ] static let red = Self(name: "Red", red: 1) static let green = Self(name: "Green", green: 1) static let blue = Self(name: "Blue", blue: 1) static let black = Self(name: "Black") static let yellow = Self(name: "Yellow", red: 1, green: 1) static let white = Self(name: "White", red: 1, green: 1, blue: 1) var swiftUIColor: SwiftUI.Color { .init(red: self.red, green: self.green, blue: self.blue) } } }

16:19

One small change we made here is that the Color type is a struct with some default values constructed rather than an enum like it was in the original episodes. This will make some things in future episodes a little nicer.

16:33

The status field is particularly interesting because it is modeled as an enum whose cases hold onto associated data. When building this demo application originally we opted to model this domain in a non-optimal way by holding onto multiple fields: //var status: Status var quantity: Int var isOnBackOrder: Bool

16:53

This allowed for some invalid states to creep into our application, such as if quantity is non-zero but isOnBackOrder is true . This may not seem like a huge deal, but these inconsistencies can leak into every dark corner of your code base. You have to be extremely defensive when handling this data to make sure it represents something valid, and that makes it hard to trust the data.

17:16

So, let’s go back to our nice enum: var status: Status // var quantity: Int // var isOnBackOrder: Bool

17:19

With this model in place we can now add a collection of these items to our view model. We could of course just add it to the main AppViewModel : class AppViewModel: ObservableObject { @Published var inventory: [Item] … }

17:34

But let’s do a little bit of upfront work to create a separate view model that is responsible just for the inventory tab. First, let’s create a new “Inventory.swift” file to house all the code of the feature. And then we can get a basic view model into place: class InventoryViewModel: ObservableObject { @Published var inventory: [Item] init(inventory: [Item] = []) { self.inventory = inventory } }

18:06

And then we can hold one of these view models in our main app-level view model: class AppViewModel: ObservableObject { @Published var inventoryViewModel: InventoryViewModel @Published var selectedTab: Tab init( inventoryViewModel: InventoryViewModel = .init(), selectedTab: Tab = .one ) { self.inventoryViewModel = inventoryViewModel self.selectedTab = selectedTab } … }

18:26

This is a good pattern to follow when dealing with tab views. The view model that sits at the level of the tab view can hold onto the view models for each tab. This allows all of the domains to be integrated together, which makes it easy for the tab view domain to observe what is happening inside each tab domain, and makes it possible to write deep, comprehensive tests.

18:46

One thing to keep in mind is that marking inventoryViewModel as a @Published field does not do exactly what you might expect since InventoryViewModel is a reference type. You will not get notified of every change inside the InventoryViewModel , but rather you will only be notified when the whole field is replaced all at once. That probably doesn’t happen too often, and so it may be more correct to actually make this field a let instead of a @Published var .

19:17

With the inventory view model in place we can now stub out a view that will be responsible for the inventory tab: struct InventoryView: View { @ObservedObject var viewModel: InventoryViewModel var body: some View { Text("Inventory") } }

19:36

And we can now swap out the InventoryView for the second tab view: InventoryView(viewModel: self.viewModel.inventoryViewModel) .tabItem { Text("Two") } .tag(Tab.two)

19:51

In fact, while we’re here we might as well rename this tab: enum Tab { case one case inventory case three } … VStack { Text("One") Button("Go to 2nd tab") { self.viewModel.selectedTab = .inventory } } .tabItem { Text("One") } .tag(Tab.one) InventoryView(viewModel: self.viewModel.inventoryViewModel) .tabItem { Text("Inventory") } .tag(Tab.inventory) … struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView( viewModel: AppViewModel( selectedTab: .inventory ) ) } }

20:04

Now we are going to quickly paste in some basic view hierarchy for the inventory view. We just want a list that holds each item in the inventory, and in each row we’ll show the item’s name, status, color as well as a button to delete the item: struct InventoryView: View { @ObservedObject var viewModel: InventoryViewModel var body: some View { List { ForEach(self.viewModel.inventory) { item in HStack { VStack(alignment: .leading) { Text(item.name) switch item.status { case let .inStock(quantity): Text("In stock: \(quantity)") case let .outOfStock(isOnBackOrder): Text( "Out of stock" + (isOnBackOrder ? ": on back order" : "") ) } } Spacer() if let color = item.color { Rectangle() .frame(width: 30, height: 30) .foregroundColor(color.swiftUIColor) .border(Color.black, width: 1) } Button(action: { /* TODO */ }) { Image(systemName: "trash.fill") } .padding(.leading) } .buttonStyle(.plain) .foregroundColor(item.status.isInStock ? nil : Color.gray) } } } }

20:29

And we’ll get a preview into place with a few inventory items already entered: struct InventoryView_Previews: PreviewProvider { static var previews: some View { InventoryView( viewModel: .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) ), ] ) ) } }

20:51

When the delete button is tapped we first want to ask for confirmation, and we’ll do this via a SwiftUI alert.

21:01

One displays alerts in SwiftUI by using the .alert modifier. Currently we are using the most recent Xcode 13 beta, with iOS 15, which has dramatically changed how alerts are handled. We are going to mitigate this by first describing how alerts worked in iOS 13 and 14, and then refactoring to use the new API style.

21:20

So, up until recently, alerts were handled by using one of two overloaded view methods: func alert( isPresented: Binding<Bool>, content: () -> Alert ) -> some View func alert<Item>( item: Binding<Item?>, content: (Item) -> Alert ) -> some View

21:33

For the first one you provide a binding of a boolean, and when that value flips to true the alert will be shown, and when it flips to false the alert will be dismissed. For the second, you provide a binding of an optional “item”, which must conform to the Identifiable protocol so that SwiftUI can differentiate between different items driving different alerts. When the item flips to something non- nil the alert will be shown, and when it flips to nil the alert will be dismissed.

21:58

Just like with the tab view, perhaps the simplest way to experiment with this API is to just use some local @State . We can introduce a boolean to control the presentation of the alert, add a button to flip it to true , and then use the .alert modifier to show an alert when the boolean is true : struct InventoryView: View { … @State var deleteItemAlertIsPresented = false var body: some View { List { … } .alert(isPresented: self.$deleteItemAlertIsPresented) { Alert( title: Text("Delete"), message: Text("Are you sure you want to delete this item?"), primaryButton: .destructive(Text("Delete")) { // TODO: handle deletion }, secondaryButton: .cancel() ) } } }

23:00

Then, to show the alert we just need to flip the boolean state to true when the delete button is tapped: Button(action: { self.deleteItemAlertIsPresented = true }) { Image(systemName: "trash.fill") }

23:11

If we run the preview we’ll see that when we tap a delete button an alert immediately appears with a cancel and delete button. When one taps on either of these buttons two things happen:

23:22

First, a value of false is written to the isPresented binding we handed to the .alert API, which means our deleteItemAlertIsPresented is also written to. This is similar to what happens with the tab view, and it’s a common pattern in SwiftUI. You will hand a binding off to a 3rd party UI component, and that UI component is free to read from it and write to it. We see it here with the boolean binding, and we saw it in the tab view when the user taps a tab the view will automatically write the associated tag to the binding. It is crucial to understand this pattern to know how SwiftUI works, and it is very powerful.

23:55

After the binding is written to, causing the alert to be dismissed, an optional action closure for the button is invoked, such as the one we stubbed out on the “Delete” button. This is where we can implement the logic to handle the alert action, which usually means just invoking a method on the view model to let it do its thing.

24:15

Right now we are only using a boolean to show and hide the alert, which means that when it flips to true we present an alert, which is defined by the Alert value we return from this closure. The title, message, buttons and actions all need to be provided with just the information available to the view in its stored fields.

24:32

This means this information is in a sense “static” because it must be provided independent of the mechanism that causes the alert to show.

24:39

In order to make the information in the alert more “dynamic” we must hold onto additional state outside of the simple boolean that controls the presentation and dismissal of the alert. For example, if we want the title of the alert to show the name of the item being deleted and if we want to have the item available in the button action closure so that we can tell the view model which item to delete, we need to hold onto some additional state: struct InventoryView: View { @ObservedObject var viewModel: InventoryViewModel @State var deleteItemAlertIsPresented = false @State var itemToDelete: Item? … }

25:09

Then when tapping the delete button we’ll capture the item we want to delete: Button( action: { self.itemToDelete = item self.deleteItemAlertIsPresented = true } ) { Image(systemName: "trash.fill") }

25:21

Now that we have this data available to use we can start customizing the alert. For example, the title can be pulled from the itemToDelete state, although we do have to deal with the fact that it is optional: .alert(isPresented: self.$deleteItemAlertIsPresented) { Alert( title: Text(self.itemToDelete?.name ?? "Delete"), … ) }

25:37

Further, we can add a new delete method on the InventoryViewModel and invoke it in the alert button’s action, though we need to do a bit of an optional dance there: class InventoryViewModel: ObservableObject { … func delete(item: Item) { self.inventory.removeAll(where: { $0.id == item.id }) } } … primaryButton: .destructive(Text("Delete")) { if let itemToDelete = self.itemToDelete { self.viewModel.delete(item: itemToDelete) } }

26:15

If we run the preview we will see that when we tap delete on an item we get an alert that has that item’s name as the title. And if we then tap “Delete” it will actually remove the item from the list.

26:23

We can even perform the removal with an animation to make it look a little nicer: func delete(item: Item) { withAnimation { self.inventory.removeAll(where: { $0.id == item.id }) } }

26:34

So this is great, our alert can now be more dynamic based on what is happening in the view. Next time: Modeling optional navigation

26:44

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

26:50

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.

27:11

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.

27:44

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…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 0160-navigation-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 .