Video #346: CloudKit Sync: Finesse
Episode: Video #346 Date: Nov 17, 2025 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep346-cloudkit-sync-finesse

Description
We round out are synchronization series with a grab bag finale. We’ll explore explicit synchronization, custom logout behavior, how the library handles read-only permissions, and how you can incorporate theses permissions in your app’s behavior.
Video
Cloudflare Stream video ID: 1ca4beb84be2a2e30c62ee042bb34b8e Local file: video_346_cloudkit-sync-finesse.mp4 *(download with --video 346)*
References
- Discussions
- SQLiteData
- the docs
- StructuredQueries
- 0346-sync-pt7
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:05
Not only are we now showing the participants for each shared reminders list in the root view of our app, but we were able to compute all of that information directly in our database query. That allowed our views to become super simple. They just display the data handed to them. And it even allowed our feature logic to be quite simple because it already had the data it needed to do its job. It didn’t need to execute extra queries just to figure out what to do. Stephen
— 0:33
And we are now nearing the end of this series of episodes, and in fact we’re near the end of our Modern Persistence mega series. But our SQLiteData library has so many little features that are packed into it that are easy to miss, we wanted to dedicate one last episode to showing off a grab bag of techniques that you can employ in your apps.
— 0:52
The first technique we will show off is how to explicitly tell the sync engine to synchronize any pending changes, including the data stored locally and data stored remotely. 99% of the time you don’t need to think about explicitly synchronization because the sync engine does all the work for you. But there are times you may want the explicit behavior, and luckily we expose all of that information for you.
— 1:16
Let’s take a look. Explicit synchronization
— 1:19
Let’s head over to the docs for SQLiteData, filter the sidebar for SyncEngine , and then navigate to the docs for the sync engine…
— 1:25
There are a bunch of methods and properties on the sync engine that are useful, but if we scroll down a bit we will see a method called fetchChanges that fetches any pending changes on the remote server: Note func fetchChanges(CKSyncEngine.FetchChangesOptions) async throws Fetches pending remote changes from the server.
— 1:35
And sendChanges that sends any locally pending changes: Note func sendChanges(CKSyncEngine.SendChangesOptions) async throws Sends pending local changes to the server.
— 1:39
And there’s a two-for-one method called syncChanges that simply fetches remote pending changes and sends locally pending changes at the same time: Note func syncChanges(fetchOptions: CKSyncEngine.FetchChangesOptions, sendOptions: CKSyncEngine.SendChangesOptions) async throws Synchronizes local and remote pending changes.
— 1:46
These methods are rarely used in applications, but if you find yourself in a situation where you would like to give your user an action they can perform to force synchronization, then these methods will help. Perhaps the most common way of supplying such an interaction is adding pull-to-refresh to a list. This is a very common UI interaction, in fact so common that some people may just expect nearly every list to have pull-to-refresh.
— 2:08
And so if you are going to have pull-to-refresh, then you need some action to perform once it is invoked. In the RemindersListsView let’s add the refreshable view modifier at the end of the list: .refreshable { }
— 2:27
This alone is all it takes to add pull-to-refresh in SwiftUI, which on its own is pretty amazing.
— 2:33
Then, whatever work you perform in this trailing closure will be tied to the lifetime of the activity indicator that displays at the top of the list.
— 2:39
We would like to invoke the syncChanges method in this trailing closure, and luckily the view already has access to the defaultSyncEngine dependency so we can just reach for it immediately: .refreshable { try await syncEngine.syncChanges() }
— 2:55
But we are not allowed to throw errors in the refreshable trailing closure, and we will decide to catch and report any error thrown: .refreshable { await withErrorReporting { try await syncEngine.syncChanges() } }
— 3:11
That is truly all it takes and we now have a way to allow our users to explicitly synchronize their changes to iCloud if they really want to.
— 3:20
And we can take it for a spin and see that pull-to-refresh has been added to the list, and the console even logs CloudKit activity when pull down.
— 3:41
There is another finesse we can make to this view that is closely related to synchronizing changes. If we take another look at the docs we will see that there is a property on the sync engine that lets you know if the sync engine is currently synchronizing changes: Note var isSynchronizing: Bool Determines if the sync engine is currently sending or receiving changes from CloudKit.
— 3:57
We can use this property to show a little progress indicator in the view so that the user knows when work is being performed to synchronize their changes. We can even add the indicator right next to the header of the first section in our list HStack { Text("My lists") .font(.largeTitle) .bold() .foregroundStyle(.black) .textCase(nil) if syncEngine.isSynchronizing { Spacer() ProgressView() } }
— 4:30
And that’s all it takes. This property is even powered by Swift’s Observation framework under the hood, which means if it ever changes it will invalidate the view, causing it to recompute its body and either show or hide the progress indicator. It’s amazing to see how the Observation framework allows us to provide tools like this that seamlessly integrate into SwiftUI views without having to think about how we will update the view when state changes. And of course, this tool can even work in UIKit if used in conjunction with our other tools, but we won’t get into any of that now. Custom logout behavior
— 5:28
Well, with just a few lines of code we have improved the user experience of our app. We now give the user a pull-to-refresh interaction to force synchronization of their data with iCloud. It’s technically not needed, but it can be nice to give users an explicit action like that to make it feel like the system reacts to their wishes. We also added a visual progress indicator to the UI so that the user knows when they data is actively being synchronized. Brandon
— 5:50
Let’s keep going. Next we are going to explore a feature of the library that was added thanks to the suggestion from someone in our community. A few weeks ago someone made a compelling case for wanting to customize the behavior of the sync engine when it detects an iCloud account has logged out. The default behavior was for the sync engine to delete all local data, after all that data ostensibly belongs to the user that just logged out. But many of Apple’s first party apps actually ask the user if they would like to delete that data or keep the data when they detect an account change.
— 6:25
Well, we recently made it possible to customize this behavior in your own apps, and so let’s take a look.
— 6:33
There is a protocol in the library called SyncEngineDelegate , and it is described as an interface for observing SyncEngine events and customizing SyncEngine behavior: /// An interface for observing SyncEngine events and customizing SyncEngine behavior. @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) public protocol SyncEngineDelegate: AnyObject, Sendable { … }
— 6:48
It currently has only one method you can implement, which is syncEngine(_:accountChanged:) : func syncEngine( _ syncEngine: SyncEngine, accountChanged changeType: CKSyncEngine.Event.AccountChange.ChangeType ) async
— 6:54
This method is called when the sync engine detects that the underlying iCloud account on the device has changed. You can interpret this event however you wish. You can choose to not clear any of the user’s local data, or you can decide to always clear the user’s data, and that is the default behavior if you do not implement this method. Or you can alert the user to let them know the iCloud account has changed and ask them how they want to handle the situation.
— 7:29
Let’s update our app to do just that. When we detect the iCloud account has changed we will prompt the user to ask if they want the local data to be cleared out. We can start by defining a new class that will conform to the SyncEngineDelegate protocol: class RemindersSyncEngineDelegate: SyncEngineDelegate { }
— 7:55
This protocol requires the conformer to be Sendable , and non-final classes typically are not. But we will see that this naturally resolves itself as we find out more and more how this object will be used, so let’s not worry about this error right now.
— 8:07
The syncEngine(_:accountChanged:) method is optional in the protocol because it comes with a default implementation that unconditionally always clears the local data. We want to change that default behavior so let’s implement that method: func syncEngine( _ syncEngine: SQLiteData.SyncEngine, accountChanged changeType: CKSyncEngine.Event.AccountChange.ChangeType ) async { }
— 8:33
This method is handed a CKSyncEngine.Event.AccountChange.ChangeType value, which is an enum with 3 cases: switch changeType { case .signIn: case .signOut: case .switchAccounts: @unknown default: break }
— 9:07
When we detect a signIn event there isn’t anything we need to do. We are only concerned with logging out and account changes: case .signIn: break
— 9:31
When we detect a sign out or an account change we’d like to mutate some state that drives an alert in the view. And it seems like this object is as good as any object to hold that state: var isDeleteLocalDataAlertPresented = false
— 9:55
And then we can flip it to true in the signOut and switchAccounts cases: case .signOut, .switchAccounts: isDeleteLocalDataAlertPresented = true
— 10:00
Further, since we want this state to drive an alert in a SwiftUI view somewhere we might as well also make this object @Observable : @Observable class RemindersSyncEngineDelegate: SyncEngineDelegate { … }
— 10:12
It may seem strange to you to make a delegate object observable, but there are no rules against that and it’s in fact kind of perfect. It’s exactly where we are being notified of events in our sync engine, and so it’s the best place to hold some state and make it possible for views to observe changes to that state.
— 10:35
And in fact, just as more observable objects need to be @MainActor , we too shall make this @MainActor : @MainActor @Observable class RemindersSyncEngineDelegate: SyncEngineDelegate { … }
— 11:08
And that has now even solved the sendability problem we had a moment ago.
— 11:13
That’s all it takes to implement this delegate. In the future the SyncEngineDelegate may introduce even more methods that allow you to listen for other events in the sync engine and customize its behavior. But for now this is the only method we have access to.
— 11:29
Now we have to figure out where to hold an instance of this object. We can instantiate this object at the entry point of the app and hold it as @State variable so that it’s only created a single time: @State var syncEngineDelegate = RemindersSyncEngineDelegate()
— 11:48
And because this object is observable we are free to derive bindings to it for alerts: .alert( "Reset local data?", isPresented: $syncEngineDelegate.isDeleteLocalDataAlertPresented ) { Button("Delete local data", role: .destructive) { } } message: { Text( """ You are no longer logged into iCloud. Would you \ like to reset your local data to the defaults? \ This will not affect your data in iCloud. """ ) }
— 12:52
And if the user chooses to delete their local data we can invoke a method on the sync engine to do so: Button("Delete local data", role: .destructive) { Task { withErrorReporting { try await syncEngine.deleteLocalData() } } }
— 13:38
That is nearly all it takes to implement this feature, but we haven’t yet hooked up this delegate object to the sync engine. To do this we just need to pass along our delegate object to the delegate argument when initializing the SyncEngine : $0.defaultSyncEngine = try! SyncEngine( for: $0.defaultDatabase, tables: RemindersList.self, Reminder.self, Tag.self, ReminderTag.self, RemindersListAsset.self, delegate: syncEngineDelegate )
— 14:08
And that’s all it takes. I can test this out by running the app in the simulator, navigating to settings, logging out, and then coming back to the app. We are immediately confronted with an alert asking us what we want to do. If I choose “Delete local data” then all of the reminders immediately disappear. And had I tapped “Cancel” the local data would have been preserved. Sharing permissions
— 15:09
And just like that we have implemented yet another feature in our reminders app that only took a few lines of code. We can now give our users the choice of clearing out their local data when the app detects they have logged out of their iCloud account. For some apps this may be the right behavior, and some apps it may not be, but at the point is that SQLiteData gives you the ability to implement this functionality if it’s what you need. Stephen
— 15:34
Let’s keep moving on. Next we are going to explore sharing permissions a bit. iCloud sharing supports permissions so that when you share a record with another user, you can decide whether to give them read-write access, or read-only access. iCloud takes care of the hard part of permissions in that it will not allow edits to records that you do not have write access to. If you do try to do that you will just get an error back when you hit CloudKit’s APIs.
— 15:57
But, there is some work we can do in our app to make this experience a lot better. Let’s take a look.
— 16:04
In order to explore permissions in iCloud I need to have a reminders list shared with me that has restricted me to read-only. And right at this moment, Brandon is off camera creating a list and sharing it with me…
— 16:33
There it is. It even already has a cover image and reminder in it and he has decided he doesn’t want me to be able to make any edits to this list. However, nothing about our app prevents me from making edits. In fact, I can swipe on the “Road trip” list, tap the info button, edit the title to be “Road trip 2026”, hit save, and even though it looked like I could edit things, the save was prevented from happening.
— 17:06
And if we look at the we will see that when we attempted to send the record to iCloud it is rejected: SQLiteData (shared.db) nextRecordZoneChangeBatch: scheduled ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ event ┃ recordType ┃ recordName ┃ ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ → Sending │ remindersLists │ 1ba4fb6f-2929-4462-b335-f5b830d3c473:remindersLists │ └───────────┴────────────────┴─────────────────────────────────────────────────────┘ SQLiteData (shared.db) stateUpdate SQLiteData (shared.db) sentRecordZoneChanges ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ action ┃ recordType ┃ recordName ┃ error ┃ ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ │ ✘ Save failed │ remindersLists │ 1ba4fb6f-2929-4462-b335-f5b830d3c473:remindersLists │ permissionFailure (10) │ └───────────────┴────────────────┴─────────────────────────────────────────────────────┴────────────────────────┘ And that caused the record to be reverted to its previous state in our local database.
— 17:14
And so on the one hand it’s nice that iCloud is at the wheel when it comes to permissions and is responsible for actually enforcing the rules. But on the other hand this is a strange user experience. We are allowing the user to edit records even though they are not allowed to. Wouldn’t it be a nicer experience if we just didn’t allow them to edit this record at all? We could even just remove the info button from the swipe actions if we detect that they do not have permission.
— 17:34
And it’s actually quite easy to do this, and we could approach it in the naive way by munging CloudKit data in the view to figure for out which lists we have write permissions, but as we’ve seen, it is better to just compute this information directly in the database query so that we have it available right away to be used in the view.
— 17:50
And we can do this using custom database functions again. First, let’s define a pure Swift function that is capable of determining if the currently logged in user has permissions to make writes to a record: func hasWritePermission() -> Bool { }
— 18:13
It turns out the CKShare object that defines the shared record has all the info we need. We can look at the docs of CKShare to see that there is a publicPermission property: Note var publicPermission: CKShare.ParticipantPermission The permission for anyone with access to the share’s URL.
— 18:27
That defines the permissions for anyone who gets access to the share’s URL. And there is further a permission property on each participant of the share: Note var permission: CKShare.ParticipantPermission The participant’s permission level for the share.
— 18:36
So we can update the Swift function to accept a CKShare as an argument so that we can inspect it: func hasWritePermission(_ share: CKShare) -> Bool { }
— 18:58
And we believe the correct thing to check in here is whether the share has public readWrite permissions, and if not, we can fallback to checking the currentUserParticipant ’s permission: share.publicPermission == .readWrite || share.currentUserParticipant?.permission == .readWrite
— 19:16
That right there should tell is whether or not the currently logged in user has write permissions for the record shared by this CKShare .
— 0:00
Let’s now make it a database function so that it can be invoked from SQL queries: @DatabaseFunction func hasWritePermission(_ share: CKShare) -> Bool { … } But because CKShare does not have a canonical representation as a SQLite type we must provide an argument to the macro to further describe how to convert the share to and from SQL types: @DatabaseFunction( as: ((CKShare.SystemFieldsRepresentation) -> Bool).self, ) func hasWritePermission(_ share: CKShare) -> Bool { … } And further this function is going to deterministic since we are just reading information off the share, and so let’s specify that: @DatabaseFunction( as: ((CKShare.SystemFieldsRepresentation) -> Bool).self, isDeterministic: true ) func hasWritePermission(_ share: CKShare) -> Bool { … }
— 19:35
And that’s all it takes to define a new database function. But we still have to install this function into the database connection: db.add(function: $hasWritePermission)
— 19:57
Now we are free to use this function in any kind of query. What if we store some extra info in our database view for permissions: @Table struct RemindersListRow { let hasWritePermission: Bool … }
— 20:17
Then we could check this state in the view to show and hide certain buttons.
— 20:21
When creating this database view we now need to supply a value for hasWritePermission , which we can compute by invoking our custom database function on the share from the sync metadata. But, we again have to contend with the fact that that share is optional, so we will map on it, and further if there is no share then we should interpret that to mean that we do have write permissions: RemindersListRow.Columns( hasWritePermission: $2.share .map { $hasWritePermission($0) } .ifnull(true), … )
— 21:19
After all, if there’s no share object then the record must be private to the user, and so of course they have write permissions. This is a great example of where the Swift type system is really helping us think through all of the edge cases of queries. As we have described before, SQL has a three-valued logic system consisting of true , false and
NULL 21:39
But because we are being forced to plug an honest boolean into the hasWritePermission argument here we have contend with this just a single time where it is must easier for us to understand that here
NULL 21:58
And now every time we query this RemindersListRow database view we will immediately have this hasWritePermission state available to us. We can even incorporate this value in a query so that we can filter for only the lists that we have write permission or don’t have write permission. And this shows how powerful database views really are. They give us a centralized place to describe the most pristine version of our data, and then our entire application gets access to that pristine data immediately.
NULL 22:26
In particular, our reminders list view now has access to this data even though we haven’t touched a line of code in this file. And in the swipe actions for a row we can consult with the hasWritePermission state to determine if we should even show that button: if row.hasWritePermission { Button { model.editButtonTapped(remindersList: row.remindersList) } label: { Image(systemName: "info.circle") } }
NULL 23:04
And just like that if I re-run the app in the simulator we will see that the list you shared with me, and only that list, is missing the info button. So we are no longer allowed to make changes to this list. And if you think the user experience of simply hiding the button is a little strange, you could go the extra mile to treat this any number of other ways. You could still show the info button, but display an alert when the user taps it letting them know they don’t have permission. Or you could display the info screen in a modal, but disable the form so that they can’t make edits. Those kinds of details highly depend on the type of app you are building, but just know that you have infinite flexibility in doing these kinds of things. Permission finesse
NULL 23:43
So we have now limited the user from editing the details of a reminders list when they don’t have permissions. All it took was registering a new custom database function to extract permissions from a CKShare , and then querying for that data in our database view. That gave us instant access to that information so that we could hide the button in the UI that allows the user to edit a reminders list. Brandon
NULL 24:03
But there are more ways in our app that the user can make edits that we want to prevent. Inside the details of a reminders list we are still allowed to complete, flag and delete reminders, and we can edit all of the details of a reminder, such as its title, notes and due date, and we can even create new reminders. Performing any of these edits on a list that I don’t have write permissions for will just result in an iCloud server error, and then those edits will be reverted in the UI.
NULL 24:31
That’s a strange user experience, so let’s make it so that the user just isn’t capable of performing any of these actions if they do not have write permissions.
NULL 24:42
If we drill into the list you shared with me we will find that the UI is still allowing me to complete, flag, and delete reminders, edit reminders, and create new reminders. Performing any of these actions results in an iCloud server error, and then the changes are reverted in the app.
NULL 28:12
We need to disable all of this functionality when the user does not have write permissions. The way the detail feature is currently modeled is that we have a big enum that describes all of the different types of details we can view: enum DetailType: Equatable { case all case completed case flagged case remindersList(RemindersList) case scheduled case today … }
NULL 28:57
Currently, viewing the details of a reminders list only has access to the bare reminder, not the RemindersListRow database view. But there’s nothing stopping us from passing along all of that data to the detail view: case remindersList(RemindersListRow)
NULL 29:37
We just need to make the database view struct Equatable , which is easy enough: @Table struct RemindersListRow: Equatable {
NULL 29:48
And then a bunch of places where we were destructuring the reminders list from this enum case we will now destructure the row value, and then pluck the reminders list off of that value: case .remindersList(let row): row.remindersList.title … case .remindersList(let row): row.remindersList.color.swiftUIColor … case .remindersList(let row): _remindersListAsset = FetchOne( RemindersListAsset.find(row.remindersList.id) ) … case .remindersList(let row): $0.remindersListID.eq(row.remindersList.id) … case .remindersList(let row): reminderForm = Reminder.Draft(remindersListID: row.remindersList.id) … case .remindersList(let row): "remindersList_\(row.remindersList.id)"
NULL 30:11
And then down in the preview we need to construct a RemindersListRow from scratch, which is easy enough to do, and shows that it will now be quite easy to tweak all of these settings for previews to make sure things work: NavigationStack { RemindersDetailView( model: RemindersDetailModel( detailType: .remindersList( RemindersListRow( hasWritePermission: true, incompleteRemindersCount: 0, isOwner: true, remindersList: remindersList, shareSummary: nil ) ) ) ) }
NULL 31:02
And finally, in the list view, when construct a DetailType enum value, we will pass along the full row value instead of just the reminders list: Button { model.detailTapped(detailType: .remindersList(row)) } label: { … }
NULL 31:27
That’s all it takes, we are in compiling order, and we now have access to the hasWritePermissions state in the detail view. And so now we can start to conditionally remove or disable certain interface elements that the user shouldn’t have access to.
NULL 31:41
To make this easier, we will add a computed property on the DetailType enum that let’s us know if the user has write permissions across all of the different detail types: var hasWritePermission: Bool { switch self { case .all, .completed, .flagged, .scheduled, .today: true case .remindersList(let row): row.hasWritePermission } }
NULL 32:23
With that done we can immediately start hiding the “New Reminder” button when the user does not have write permissions: if model.detailType.hasWritePermission { Button { model.newReminderButtonTapped() } label: { HStack { Image(systemName: "plus.circle.fill") Text("New Reminder") } .bold() .font(.title3) } }
NULL 32:51
If we run the app in the simulator we will now see that I no longer have the “New Reminder” button in the list that you shared with me.
NULL 33:00
The next interface elements we want to remove are all held in the ReminderRow view. So we have to pass along the permission information to it: struct ReminderRow: View { … let hasWritePermission: Bool … }
NULL 33:40
And then, in the detail view we can now pass along the value by referencing the computed property we added a moment ago: ForEach(model.rows, id: \.reminder.id) { row in ReminderRow( color: model.detailType.color, hasWritePermission: model.detailType.hasWritePermission, isPastDue: row.isPastDue, reminder: row.reminder, tags: row.tags ) { model.reminderDetailsButtonTapped(reminder: row.reminder) } }
NULL 33:54
Now everything is compiling, and we are in a position to customize the row view depending on what kind of permissions the current user has. First let’s decide what to do with the button that allows us to complete and un-complete reminders. We could just disable it so that it’s still present but just not functional: Button { … } label: { … } .disabled(!hasWritePermission) Or we could just completely omit it: if hasWritePermission { Button { … } label: { … } .disabled(!hasWritePermission) } It really is up to you for how you want to handle this, but for now we will just remove the button entirely.
NULL 34:31
We will also remove the info button on the trailing end of the row: if hasWritePermission { Button { onDetailsTapped() } label: { Image(systemName: "info.circle") } .tint(color) }
NULL 34:41
And we will remove all swipe actions: .swipeActions { if hasWritePermission { … } }
NULL 34:55
And just like that we have removed all ways the user can edit a reminder on this screen. And if we run the app we will see it works as expected. All buttons that can lead to an editing action have been removed. However, if someone who has write access to this list makes edits, then those changes will be sync’d to this device just fine. We just can’t edit it ourselves. Conclusion
NULL 36:13
Alright, we are finally done with our iCloud synchronization series, and we have covered an incredible amount of material. We have shown how to prepare an existing app for iCloud synchronization, even one that was originally built without every once thinking about a future of synchronization. We have showed how to synchronize all data in a SQLite database, including assets. We have shown how records and their associations can be shared with other iCloud users. We’ve shown how to query the iCloud sync metadata from within SQLite itself And we have now shown some tips and tricks to add a special layer of finesse to an application using our SQLiteData library. Stephen
NULL 36:52
And this also concludes our Modern Persistence super series, at least for now. We do have a bit more to say about our SQLiteData library to round out the year, but next year we are excited to get into some brand new topics. Until next time. References SQLiteData Brandon Williams & Stephen Celis A fast, lightweight replacement for SwiftData, powered by SQL. https://github.com/pointfreeco/sqlite-data StructuredQueries A library for building SQL in a safe, expressive, and composable manner. https://github.com/pointfreeco/swift-structured-queries Downloads Sample code 0346-sync-pt7 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 .