<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://mackuba.eu</id>
  <title>MacKuba.eu</title>
  <subtitle>Kuba Suder's blog on Mac/iOS &amp; web dev</subtitle>
  <link href="https://mackuba.eu" rel="alternate" type="text/html"/>
  <link href="https://mackuba.eu/feed-notes.xml" rel="self" type="application/atom+xml"/>
  <updated>2026-02-16T03:38:55+01:00</updated>
  <author>
    <name>Kuba Suder</name>
    <email>jakub.suder@gmail.com</email>
  </author>
  <entry>
    <id>https://mackuba.eu/notes/wwdc22/swiftui-cookbook-for-navigation/</id>
    <title>The SwiftUI cookbook for navigation</title>
    <published>2022-07-03T20:54:35+02:00</published>
    <updated>2022-07-03T20:54:35+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc22/swiftui-cookbook-for-navigation/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;This year SwiftUI adds new APIs for handling navigation which scale well from simple to complex UIs, include support for programmatic navigation and deep linking&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;The existing API&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The existing API is based on links (&lt;code&gt;NavigationLink&lt;/code&gt;) embedded inside &lt;code&gt;NavigationView&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You have a list of navigation link buttons, each specifying the view it links to, and when the button is tapped, the specified view is pushed onto the stack&lt;/p&gt;
&lt;p&gt;This works great for basic navigation, and you can continue using this pattern with the new API&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To programmatically perform navigation, you could use the &lt;code&gt;NavigationView&lt;/code&gt; initializer that binds the state of the navigation link to a variable (using &lt;code&gt;isActive&lt;/code&gt; or &lt;code&gt;selection&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;However, this only worked well in simple cases and didn't let you easily do things like deep-linking deep into the navigation hierarchy straight from the root, or moving back to the root from there&lt;/p&gt;
&lt;p&gt;It also required you to keep separate bindings for each link, or at least each level of the hierarchy&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;The new navigation APIs&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The new API lets you bind the state of the whole navigation stack to a single array managed by the root container&lt;/p&gt;
&lt;p&gt;Navigation links push additional values into that array when they're selected&lt;/p&gt;
&lt;p&gt;This allows you to quickly move through the hierarchy by modifying that array binding, e.g. to deep-link to a specific view by assigning a collection of values to it, or to pop the stack to the root view immediately by removing all values&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Navigation containers:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The new API consists of a couple of new container views and a new version of the navigation link&lt;/p&gt;
&lt;p&gt;The first container is &lt;code&gt;NavigationStack&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationStack(path: $path) {
    RecipeDetail()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NavigationStack&lt;/code&gt; is used for simple push-pop navigation happening in a single container, like in iPhone apps&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The second container is &lt;code&gt;NavigationSplitView&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationSplitView {
    RecipeCategories()
} detail: {
    RecipeGrid()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NavigationSplitView&lt;/code&gt; is perfect for multi-column apps like Mail or Notes on the Mac or iPad&lt;/p&gt;
&lt;p&gt;It automatically adapts to a single-column view in contexts like iPhone, Apple TV or slide-over on the iPad&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To create a three-column layout, use another version of the initializer:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationSplitView {
    RecipeCategories()
} content: {
    RecipeList()
} detail: {
    RecipeDetail()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NavigationSplitView&lt;/code&gt; has plenty of options that let you customize things like column widths or sidebar presentation, or even programmatically show or hide columns&lt;/p&gt;
&lt;p&gt;You can hear more about configuring a layout like this in the talk &lt;a href="https://developer.apple.com/videos/play/wwdc2022/10058/"&gt;SwiftUI on iPad: Organize your interface&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Navigation links:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Previously, a navigation link included a title and a destination view to be presented:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationLink("Show detail") {
    DetailView()
}

NavigationLink("Show detail", destination: DetailView())

NavigationLink(destination: DetailView()) {
    Text(title)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The new version of &lt;code&gt;NavigationLink&lt;/code&gt; still includes a title, but instead of a view to present, it provides a value associated with the link:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationLink("Apple Pie", value: applePieRecipe)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Clicking the link pushes that value into its container's path array&lt;/p&gt;
&lt;p&gt;However, the exact view that's pushed when the link is clicked is determined by the navigation stack that the link is contained in&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Navigation patterns&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Let's now look at the ways you can use these views to build navigation in your apps:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;1. Basic stack:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The first pattern is a basic navigation stack like in the Settings app on the iPhone or Apple Watch&lt;/p&gt;
&lt;p&gt;The main view presents a list of items, and when an item is selected, it pushes a detail view on the stack which fills the whole screen&lt;/p&gt;
&lt;p&gt;This approach works on all platforms, but it's best suited for iPhone, Apple TV and Apple Watch&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In the basic form, you can just replace the &lt;code&gt;NavigationView&lt;/code&gt; with &lt;code&gt;NavigationStack&lt;/code&gt; and use &lt;code&gt;NavigationLinks&lt;/code&gt; like before, with the destination view inline:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var body: some View {
    NavigationStack {
        List(Category.allCases) { category in
            Section(category.localizedName) {
                ForEach(dataModel.recipes(in: category)) { recipe in
                    NavigationLink(recipe.name) {
                        RecipeDetail(recipe: recipe)
                    }
                }
            }
        }
        .navigationTitle("Categories")
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To be able to perform navigation programmatically, assign a value to the navigation link instead:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ForEach(dataModel.recipes(in: category)) { recipe in
    NavigationLink(recipe.name, value: recipe)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;And the destination view definition is then pulled out of the link block and moved to a new &lt;code&gt;.navigationDestination&lt;/code&gt; modifier defined on the &lt;code&gt;NavigationStack&lt;/code&gt; container:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.navigationDestination(for: Recipe.self) { recipe in
    RecipeDetail(recipe: recipe)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The modifier describes what view should be pushed onto the stack when a given value is pushed into container's path by any of the navigation links anywhere in the view hierarchy (or programmatically), for all values of the given type&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The navigation stack's path starts out initially as an empty array &lt;code&gt;[]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When the navigation link is clicked:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;1. It appends the assigned value of &lt;code&gt;Recipe&lt;/code&gt; type into the path&lt;/li&gt;
&lt;li&gt;2. The navigation stack then checks the &lt;code&gt;navigationDestination&lt;/code&gt; configuration which stores a mapping of &lt;code&gt;Recipe -&amp;gt; some View&lt;/code&gt; to get an appropriate view to present&lt;/li&gt;
&lt;li&gt;3. The new view is pushed onto the view stack&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;When you press the "Back" button, the last item is removed from the path array and the top view on the stack is popped&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You also need to create a binding of the navigation stack's path to a local state variable:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@State private var path: [Recipe] = []&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p class="arrow"&gt;→ you can use an array of a specific type if you only use values of one type in the path; if you want to build mixed paths of multiple types, use the new &lt;code&gt;NavigationPath&lt;/code&gt; wrapper type instead&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Pass a binding to &lt;code&gt;NavigationStack&lt;/code&gt;'s initializer:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var body: some View {
    NavigationStack(path: $path) {
        ...
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now modify the view stack by assigning to the state variable:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func showRecipeOfTheDay() {
    path = [dataModel.recipeOfTheDay]
}

func popToRoot() {
    path.removeAll()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can see the &lt;code&gt;NavigationStack&lt;/code&gt; in action in the talk &lt;a href="https://developers.apple.com/videos/play/wwdc2022/10133"&gt;Build a productivity app for Apple Watch&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;2. Multi-column presentation without stacks:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This interface is similar to the Mail app on the Mac and iPad&lt;/p&gt;
&lt;p&gt;The layout consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a sidebar, initially hidden, which shows a list of categories&lt;/li&gt;
&lt;li&gt;the second column that shows a list of recipes&lt;/li&gt;
&lt;li&gt;the main area that shows the recipe details&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This interface is great on larger devices like the iPad and Mac&lt;/p&gt;
&lt;p&gt;It allows you to see more information at the same time on the screen&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In the first column (sidebar), we add a list of navigation links for each category of recipes:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@State private var selectedCategory: Category?

var body: some View {
    NavigationSplitView {
        List(Category.allCases, selection: $selectedCategory) { category in
            NavigationLink(category.localizedName, value: category)
        }
        .navigationTitle("Categories")
    } content: {
        // ... second column ...
    } detail: {
        // ... detail view ...
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Instead of binding a property to the navigation view's path like before, here we bind it instead to the selected item in the list, on the root level only&lt;/p&gt;
&lt;p&gt;When a &lt;code&gt;NavigationLink&lt;/code&gt; is contained within a list with the same selection type as the navigation link's value, the link will automatically update the selection of the list when clicked&lt;/p&gt;
&lt;p class="arrow"&gt;→ see more about this in the &lt;a href="https://developer.apple.com/videos/play/wwdc2022/10058/"&gt;SwiftUI on iPad: Organize your interface&lt;/a&gt; talk&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In the second column (content), we show the same kind of list, listing recipes in the selected category, and again, we use &lt;code&gt;NavigationLinks&lt;/code&gt; inside with assigned values of the same type as the list's selection binding&lt;/p&gt;
&lt;p&gt;When a recipe is selected in the second column, the recipe details will be shown in the main area:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@State private var selectedCategory: Category?
@State private var selectedRecipe: Recipe?

var body: some View {
    NavigationSplitView {
        // ...
    } content: {
        List(dataModel.recipes(in: selectedCategory), selection: $selectedRecipe) { recipe in
            NavigationLink(recipe.name, value: recipe)
        }
        .navigationTitle(selectedCategory?.localizedName ?? "Recipes")
    } detail: {
        RecipeDetail(recipe: selectedRecipe)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Note: unlike &lt;code&gt;NavigationStack&lt;/code&gt;, &lt;code&gt;NavigationSplitView&lt;/code&gt; *does not* keep a path property with a list of selected items that you can bind to&amp;nbsp;– the navigation on this level is managed only through the selection bindings of the two lists:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func showRecipeOfTheDay() {
    let recipe = dataModel.recipeOfTheDay

    selectedCategory = recipe.category
    selectedRecipe = recipe
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NavigationSplitView&lt;/code&gt; also does not use the &lt;code&gt;.navigationDestination&lt;/code&gt; modifier&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In environments that don't support multi-pane views (iOS, tvOS and iPad apps in split view or slide-over), &lt;code&gt;NavigationSplitView&lt;/code&gt; renders as a single-pane &lt;code&gt;NavigationStack&lt;/code&gt; which pushes the selected views onto its stack&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;3. Putting the two modes together&amp;nbsp;– a multi-column view with a stack:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Here, we're building a two-column layout like in the Photos app on the iPad and Mac:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the left column (sidebar) shows a list of categories&lt;/li&gt;
&lt;li&gt;the main area will show recipe photos in a grid&lt;/li&gt;
&lt;li&gt;when a recipe is selected, a recipe details view will be pushed on the navigation stack within the detail area&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To build this kind of layout, we use &lt;code&gt;NavigationSplitView&lt;/code&gt; and &lt;code&gt;NavigationStack&lt;/code&gt; together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;1. The first column displays a list like in the three-pane version&lt;/li&gt;
&lt;li&gt;2. The detail area contains a navigation stack like in the first example&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;We track the state of the navigation using two variables: a list selection binding for the split view's first column, and a path of values for the stack view inside the second column:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@State private var selectedCategory: Category?
@State private var path: [Recipe] = []

struct ContentView: {
    var body: some View {
        NavigationSplitView {
            List(Category.allCases, selection: $selectedCategory) { category in
                NavigationLink(category.localizedName, value: category)
            }
            .navigationTitle("Categories")
        } detail: {
            NavigationStack(path: $path) {
                RecipeGrid(category: selectedCategory)
            }
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The detail view displays a grid of navigation buttons wrapping image tiles; when a tile is pressed, the detail screen for a recipe is pushed onto the stack within the detail pane, which is defined using &lt;code&gt;NavigationLinks&lt;/code&gt; and the &lt;code&gt;.navigationDestination&lt;/code&gt; modifier:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;struct RecipeGrid: View {
    var category: Category?

    var body: some View {
        if let category = category {
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(dataModel.recipes(in: category)) { recipe in
                        NavigationLink(value: recipe) {
                            RecipeTile(recipe: recipe)
                        }
                    }
                }
            }
            .navigationTitle(category.name)
            .navigationDestination(for: Recipe.self) { recipe in
                RecipeDetail(recipe: recipe)
            }
        } else {
            Text("Select a category")
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To update navigation programmatically, assign the two bound variables to modify selection in the two panes:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func showRecipeOfTheDay() {
    let recipe = dataModel.recipeOfTheDay

    selectedCategory = recipe.category
    path = [recipe]
}&lt;/pre&gt;
&lt;h3&gt;Persisting navigation state&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Navigation state can be persisted between launches with the use of &lt;code&gt;Codable&lt;/code&gt; and &lt;code&gt;@SceneStorage&lt;/code&gt; properties&lt;/p&gt;
&lt;p&gt;We'll wrap the navigation state properties into a single model type, so that they can be persisted together to keep the state consistent&lt;/p&gt;
&lt;p&gt;This model type will conform to &lt;code&gt;Codable&lt;/code&gt; to let us save &amp;amp; restore it&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@SceneStorage&lt;/code&gt; will be used to automatically persist it together with the scene&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Looking at the last example (with both &lt;code&gt;NavigationSplitView&lt;/code&gt; and &lt;code&gt;NavigationStack&lt;/code&gt;), here's how we update it to bind the UI to the properties in the new model type:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@StateObject private var navModel = NavigationModel()

var body: some View {
    NavigationSplitView {
        List(Category.allCases, selection: $navModel.selectedCategory) { category in
            NavigationLink(category.localizedName, value: category)
        }
        .navigationTitle("Categories")
    } detail: {
        NavigationStack(path: $navModel.path) {
            RecipeGrid(category: navModel.selectedCategory)
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;One thing to note about the &lt;code&gt;NavigationModel&lt;/code&gt;: the values used for navigation &amp;amp; list selection will often be your model data types, i.e. whole structs like &lt;code&gt;Recipe&lt;/code&gt; here, but we don't want to store the whole structs with all their properties as a part of the navigation path&amp;nbsp;– it would be redundant, because we probably have all the recipe data stored somewhere else in some kind of database.&lt;/p&gt;
&lt;p&gt;To store the navigation state, we only need some kind of references to them, like their IDs&amp;nbsp;– so we're going to customize the &lt;code&gt;Codable&lt;/code&gt; conformance here to save the recipes in the path as their ID values and restore them back from that on load:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;class NavigationModel: ObservableObject, Codable {
    @Published var selectedCategory: Category?
    @Published var path: [Recipe] = []

    enum CodingKeys: String, CodingKey {
        case selectedCategory
        case recipePathIds
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeIfPresent(selectedCategory, forKey: .selectedCategory)
        try container.encode(path.map(\.id), forKey: .recipePathIds)
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.selectedCategory = try container.decodeIfPresent(Category.self,
                                        forKey: selectedCategory)

        let recipePathIds = try container.decode([Recipe.ID].self, forKey: .recipePathIds)

        // some recipes might have been deleted in the meantime, so skip those
        self.path = recipePathIds.compactMap { DataModel.shared[$0] }
    }

    // helper to serialize the model to/from JSON Data
    var jsonData: Data? {
        get { ... }
        set { ... }
    }

    // async sequence of updates (from the sample code project)
    var objectWillChangeSequence: AsyncPublisher&amp;lt;Publishers.Buffer&amp;lt;ObservableObjectPublisher&amp;gt;&amp;gt; {
        objectWillChange
            .buffer(size: 1, prefetch: .byRequest, whenFull: .dropOldest)
            .values
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The one missing piece is persisting the model using &lt;code&gt;@SceneStorage&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;SceneStorage&lt;/code&gt; can only hold values of simple types like &lt;code&gt;URL&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt; or &lt;code&gt;Data&lt;/code&gt;, and not complex object types, we need to manually restore the model from the persisted form and make sure it's kept in sync after every change; &lt;code&gt;SceneStorage&lt;/code&gt; will handle the persistence of the encoded value for us, but we need to handle the conversion to/from it&lt;/p&gt;
&lt;p&gt;We do this by providing a &lt;code&gt;.task&lt;/code&gt; that will run when the view is displayed, which loads the model from the data&lt;/p&gt;
&lt;p&gt;The task then subscribes to a sequence of changes from the model and encodes the model into storage every time it changes:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@StateObject private var navModel = NavigationModel()
@SceneStorage("navigation") private var data: Data?

var body: some View {
    NavigationSplitView {
        // ...
    }
    .task {
        if let data {
            navModel.jsonData = data
        }
        for await _ in navModel.objectWillChangeSequence {
            data = navModel.jsonData
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;For more details and examples about the new APIs, check out the "&lt;a href="https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types"&gt;Migrating to new navigation types&lt;/a&gt;" documentation article&lt;/p&gt;
&lt;p&gt;See also "&lt;a href="https://developer.apple.com/videos/play/wwdc2022/10061/"&gt;Bring multiple windows to your SwiftUI app&lt;/a&gt;" for info about opening new windows and scenes&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc22/whats-new-in-appkit/</id>
    <title>What's new in AppKit</title>
    <published>2022-06-08T23:57:53+02:00</published>
    <updated>2022-06-08T23:57:53+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc22/whats-new-in-appkit/"/>
    <content type="html">&lt;h3&gt;Stage Manager&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;New UI workflow that cleans up inactive windows in your workspace, while your active window takes center stage, letting you put the focus on one task at a time&lt;/p&gt;
&lt;p&gt;You can also pull windows into sets that are swapped in and out as a group&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This has an impact on how your app's windows present themselves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;newly presented windows replace those on the stage to keep the workspace tidy&lt;/li&gt;
&lt;li&gt;auxiliary windows like panels, popovers, settings windows cohabitate with primary windows&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This should mostly work as expected using existing &lt;code&gt;NSWindow&lt;/code&gt; APIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stage Manager won't swap out a window if it's a floating panel, a modal window, or a window with &lt;code&gt;toolbarStyle&lt;/code&gt; set to &lt;code&gt;.preference&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it also obeys the window's &lt;code&gt;collectionBehavior&lt;/code&gt; flags (an old API from OS X 10.5-10.7 which mostly defines how a window behaves within desktop spaces, Exposé and full screen mode): a window will not displace an active window in center stage if it includes the &lt;code&gt;.auxiliary&lt;/code&gt;, &lt;code&gt;.moveToActiveSpace&lt;/code&gt;, &lt;code&gt;.stationary&lt;/code&gt; or &lt;code&gt;.transient&lt;/code&gt; collection behavior flags&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Preferences&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The System Preferences app now has a whole new look, with a different navigation and new design&lt;/p&gt;
&lt;p&gt;To align with Settings apps on other platforms, it's been renamed to System Settings&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you're shipping a custom preference pane bundle, it will continue to work in the new Settings app&lt;/p&gt;
&lt;p&gt;Your custom pane will appear in the Settings app sidebar and will be loaded on first access, like in Monterey and earlier versions&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;In-app settings:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To match the newly renamed System Settings app, your app's Preferences menu entry in the app's main menu is now renamed to "Settings…"&lt;/p&gt;
&lt;p&gt;The menu item's label is automatically updated if you build the app with the latest SDK&lt;/p&gt;
&lt;p&gt;Check your app for any other uses of the word "Preferences" in the UI referring to the preferences window and update them accordingly&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;New form design:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is a new interface style for control-rich forms like the views commonly used in settings windows&lt;/p&gt;
&lt;p&gt;It's designed to present an interface that displays a lot of controls in a clear, organized way&lt;/p&gt;
&lt;p&gt;In this kind of UI, the form provides a lot of visual structure, so many system controls (like popup buttons) automatically adapt to this context by drawing normally with a lower visual weight and only revealing a heavier border or background on hover&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can build a form like this using the SwiftUI &lt;code&gt;Form&lt;/code&gt; element with an &lt;code&gt;.insetGrouped&lt;/code&gt; style:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Form {
    TextField("Computer Name", text: $name)
    Toggle("Screen Sharing", isOn: $screenSharing)
    Toggle("File Sharing", isOn: $fileSharing)
    Picker("AirDrop", selection: $airdrop) {
        ForEach(AirDropVisibility.allCases) {
            Text($0.label).tag($0)
        }
    }
}
.formStyle(.insetGrouped)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This renders &lt;code&gt;Picker&lt;/code&gt; as a borderless popup button, &lt;code&gt;TextField&lt;/code&gt; as a borderless inline text field, and &lt;code&gt;Toggle&lt;/code&gt; as tiny switches&lt;/p&gt;
&lt;p&gt;SwiftUI automatically handles the visual style, layout and the scrolling behavior of such form&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you aren't using SwiftUI yet, then… well… this is a good moment to start 😅&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Control updates&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;NSComboButton:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A combo button is a new control&amp;nbsp;– a button which provides an immediate action and a menu for additional options&lt;/p&gt;
&lt;p&gt;It combines a standard button and a pull-down menu into a single control&lt;/p&gt;
&lt;p&gt;This design is commonly used for use cases like a "move to folder" button in Mail, which moves an email to the pre-selected folder if you click the main left part of the button (the primary action), but also lets you pick an alternative location from the menu if you click the arrow on the right side&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are two styles of the combo button which change its appearance and behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;split style (the default), with a separator between the main button part and the arrow part&lt;/li&gt;
&lt;li&gt;unified style&amp;nbsp;– looks like a normal button, performs the primary action on click, and only presents the menu if you click and hold&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;NSColorWell:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NSColorWell&lt;/code&gt; has a new default design, with a white border and rounded corners&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are also two optional new styles you can pick:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;1) A "minimal" style:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no borders, filled completely with color&lt;/li&gt;
&lt;li&gt;a disclosure arrow appears on hover&lt;/li&gt;
&lt;li&gt;it shows a color selection UI in a popover&amp;nbsp;– by default a standard system grid of colors, but you can customize what's shown there&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;2) An "expanded" style:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;used in iWork apps previously&lt;/li&gt;
&lt;li&gt;combines both interaction models: a borderless minimal style on the left, with a disclosure arrow and a popover, and a button part which shows the full color picker on the right&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Select the style using the &lt;code&gt;colorWellStyle&lt;/code&gt; property&lt;/p&gt;
&lt;p&gt;The behavior of the popover in "minimal" and "expanded" modes is configured through a new target-action pair: &lt;code&gt;pulldownTarget&lt;/code&gt; + &lt;code&gt;pulldownAction&lt;/code&gt; (if nil, then a standard system popover is used)&lt;/p&gt;
&lt;p class="arrow"&gt;→ you can use this to present a custom popover, or a completely different UI like a menu&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Toolbars:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are two new delegate methods of &lt;code&gt;NSToolbar&lt;/code&gt; that let you customize the toolbar behavior:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;1) &lt;code&gt;toolbarImmovableItemIdentifiers(_:)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;defines a list of toolbar items that the user shouldn't be able to move or remove (they also don't animate when you enter the toolbar editing view)&lt;/li&gt;
&lt;li&gt;used e.g. in Mail app for a filter button that should always appear above the messages list&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;2) &lt;code&gt;toolbar(_:, itemIdentifier:, canBeInsertedAt:)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allows you to manually approve any reordering, insertion or removal of a toolbar item&lt;/li&gt;
&lt;li&gt;use this to implement a custom set of toolbar customization rules, e.g. an item that has to stay within one toolbar section&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Centered toolbar section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a new property &lt;code&gt;centeredItemIdentifiers&lt;/code&gt; allows you to specify multiple centered items, which have to stay within the toolbar's center section&lt;/li&gt;
&lt;li&gt;previously there could be only one centered item, specified using &lt;code&gt;centeredItemIdentifier&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Preventing item resizing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if a toolbar item changes its label depending on the state (e.g. "Mute / Unmute" in Mail app), it may have to change its size and cause other nearby buttons to shift back and forth when switching&lt;/li&gt;
&lt;li&gt;to prevent this, you can now list all possible labels in the &lt;code&gt;possibleLabels&lt;/code&gt; property of an &lt;code&gt;NSToolbarItem&lt;/code&gt;; the toolbar will automatically position items according to their longest possible label so that they don't have to be resized&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Alerts design:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is a slight update to the design of alerts&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Alerts in macOS Big Sur and above have a new, compact layout with centered text, which is optimized for small amounts of text accompanied by a few clear choices&lt;/p&gt;
&lt;p&gt;In general, this is how an alert should look: alerts work best with shorter text, which your users are more likely to read before dismissing the dialog&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;However, sometimes you really can't make the description shorter, especially when the alert is about something very important like deleting a disk volume&lt;/p&gt;
&lt;p&gt;For these cases, there is now a new expanded look of alerts which is more optimal for longer text&lt;/p&gt;
&lt;p&gt;The expanded style uses a wider window, horizontally arranged buttons, and left-aligned text&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is no option to enable this&amp;nbsp;– expanded style is used automatically when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the description text is too long to fit comfortably in the compact size&lt;/li&gt;
&lt;li&gt;or if the alert has an accessory view that's too large to fit in a compact alert&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Note: the layout variant is chosen at the time when the alert is presented, so the alert is not resized if the text is changed after it's displayed&lt;/p&gt;
&lt;p&gt;You should still aim to reduce the length of the alert text whenever possible, but this should improve the UI for those cases when you can't do that&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;NSTableView performance updates:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NSTableView&lt;/code&gt; is designed to efficiently handle a very large number of rows&lt;/p&gt;
&lt;p&gt;It does this by lazily populating and reusing views as the table is scrolled&lt;/p&gt;
&lt;p&gt;This becomes a challenge when the views have different heights, because it needs to calculate the total height of the table and the position of each row&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Previously, &lt;code&gt;NSTableView&lt;/code&gt; did this be pre-calculating the size of each row in the table, which impacts the initial load time&lt;/p&gt;
&lt;p&gt;In macOS Ventura &lt;code&gt;NSTableView&lt;/code&gt; now calculates the row heights lazily:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;row heights are calculated for rows which are in or near the scrolling viewport&lt;/li&gt;
&lt;li&gt;for the rest of the rows, &lt;code&gt;NSTableView&lt;/code&gt; estimates the height based on the row heights that it has already measured&lt;/li&gt;
&lt;li&gt;as the table is scrolled, the table view requests more row heights as needed and replaces the estimates with real measurements&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This optimization significantly improves load times for very large tables&lt;/p&gt;
&lt;p&gt;It's automatically used for all apps on macOS Ventura and doesn't require any changes&lt;/p&gt;
&lt;p&gt;Note that this might affect the timing of delegate callbacks like &lt;code&gt;tableView(_:, heightOfRow:)&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;SF Symbols&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;SF Symbols 4 adds more than 450 new symbols, including things like new currency symbols, household items or sports objects&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;SF Symbols can be used with one of 4 styles: monochrome, hierarchical, palette and multicolor&lt;/p&gt;
&lt;p&gt;In macOS Ventura, symbols may now specify a "preferred rendering mode"&amp;nbsp;– one of these styles that is preferred for this specific symbol and is used automatically if not configured otherwise&lt;/p&gt;
&lt;p&gt;You can still use &lt;code&gt;NSImageSymbolConfiguration&lt;/code&gt; to override the preferred style&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Variable symbols:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is a new type of symbol which represents some value or quantity, like WiFi signal strength or volume&lt;/p&gt;
&lt;p&gt;These symbols have several versions depending on the value, with a smaller or larger part of the symbol filled or colored&lt;/p&gt;
&lt;p&gt;You configure these symbols by providing a floating point value between 0 and 1:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;class NSImage {
    public init?(symbolName: String,
                 variableValue: Double,
                 accessibilityDescription: String?)

    public init?(systemSymbolName: String,
                 variableValue: Double,
                 accessibilityDescription: String?)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;If the symbol doesn't define any thresholds depending on the value, the value is ignored&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Sharing&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;macOS Ventura includes a new redesigned share sheet, which now looks similar to the one on iOS&lt;/p&gt;
&lt;p&gt;It includes a list of suggested people to share with, like the iOS one&lt;/p&gt;
&lt;p&gt;It also adds new APIs which apps can use to let the user invite their contacts to collaborate on a document&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The share sheet is accessed using the existing API &lt;code&gt;NSSharingServicePicker&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can still filter the list of services and add custom ones, use existing delegate callbacks etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you share a file URL, the sheet will automatically display the file name, type, size and icon in the header&lt;/p&gt;
&lt;p&gt;If you're sharing a custom item, you can manually provide details to be displayed in the header using a new protocol &lt;code&gt;NSPreviewRepresentableActivityItem&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;protocol NSPreviewRepresentableActivityItem: AnyObject {
    /* The item to be shared */
    var item: Any { get }

    /* A localized string representing the item's name or title */
    optional var title: String? { get }

    /* A provider for a full-sized image that represents the item */
    optional var imageProvider: NSItemProvider? { get }

    /* A provider for a thumbnail-sized icon that represents the item */
    optional var iconProvider: NSItemProvider? { get }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;or the helper class &lt;code&gt;NSPreviewRepresentingActivityItem&lt;/code&gt; which implements the protocol:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;class NSPreviewRepresentingActivityItem: NSPreviewRepresentableActivityItem {
    init(item: Any, title: String?, image: NSImage?, icon: NSImage?)
    init(item: Any, title: String?, imageProvider: NSItemProvider?, iconProvider: NSItemProvider?)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use the preview class if you already have all the data you need, and the protocol with &lt;code&gt;NSItemProviders&lt;/code&gt; if it's too performance-intensive to generate it up front&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you want to launch the share sheet from a menu, like the main menu bar or a context menu when clicking a collection view item, the share sheet API now provides a standard "Share…" menu item that you can put in any menu to display the sheet:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;class NSSharingServicePicker {
    var standardShareMenuItem: NSMenuItem
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In case of context menus, the displayed popover will be anchored to the same view on which the context menu was shown&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There's a lot more new features for managing collaboration&lt;/p&gt;
&lt;p&gt;See more in separate talks: "&lt;a href="https://developer.apple.com/videos/play/wwdc2022/10095/"&gt;Enhance collaboration experiences with Messages&lt;/a&gt;" and "&lt;a href="https://developer.apple.com/videos/play/wwdc2022/10093/"&gt;Integrate your custom collaboration app with Messages&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/add-rich-graphics-to-swiftui-app/</id>
    <title>Add rich graphics to your SwiftUI app</title>
    <published>2022-06-04T22:50:55+02:00</published>
    <updated>2022-06-04T22:50:55+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/add-rich-graphics-to-swiftui-app/"/>
    <content type="html">&lt;h3&gt;Safe area&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;By default SwiftUI positions your content within the safe area, so that it doesn't get obscured by things at the top and the bottom of the screen, like the navigation bar and the home bar on Face ID phones&lt;/p&gt;
&lt;p&gt;If you do want the view to extend to the whole screen, edge to edge, below the top/bottom bars (e.g. on screens that show some kind of graphics), you can opt out of the safe area using &lt;code&gt;.ignoresSafeArea&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ContentView()
    .ignoresSafeArea()&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also choose on which sides you want to extend the view into the safe area:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ContentView()
    .ignoresSafeArea(edges: .bottom)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You don't need to do this on most screens, since most content should be kept within the safe area&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The keyboard, if visible, is also outside the safe area&amp;nbsp;– it actually has its own inner safe area that's positioned inside the main "container safe area"&lt;/p&gt;
&lt;p&gt;By default your view is positioned within the keyboard's safe area, so that it doesn't get covered by the keyboard&amp;nbsp;– to opt out just of the keyboard safe area (but stay within container safe area), add the additional &lt;code&gt;.keyboard&lt;/code&gt; parameter:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ContentView()
    .ignoresSafeArea(.keyboard)&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Backgrounds&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are now new versions of the &lt;code&gt;.background&lt;/code&gt; modifier where if you don't pass any specific color or style, it uses the default screen background (white or black, depending on light/dark mode):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;HStack {
    ...
}
.background()&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;These backgrounds (and backgrounds like &lt;code&gt;.background(Color.green)&lt;/code&gt; now automatically extend below the safe area, unless you customize it&lt;/p&gt;
&lt;p&gt;You can also specify that the background should only be used within a defined shape around the view, like a rounded rectangle:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;HStack {
    ...
}
.background(.cyan, in: RoundedRectangle(cornerRadius: 12))&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In this case, the view and its entire background area both stay within the safe area&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Materials and vibrancy&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Materials are a new kind of backgrounds&amp;nbsp;– the kind of translucent blur background used in various system areas:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.background(.regularMaterial)
.background(.thickMaterial, in: RoundedRectangle(cornerRadius: 12))&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Materials are great if you want to show some of the colorful content positioned below some kind of panel bleeding into its background&lt;/p&gt;
&lt;p&gt;There's a set of materials: &lt;code&gt;.ultraThin&lt;/code&gt;, &lt;code&gt;.thin&lt;/code&gt;, &lt;code&gt;.regular&lt;/code&gt;, &lt;code&gt;.thick&lt;/code&gt;, &lt;code&gt;.ultraThick&lt;/code&gt;&amp;nbsp;– the "thinner" the material, the more of the color from behind comes through&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Some of the content displayed over a material background (using foreground colors of "secondary" and below) uses an effect called &lt;em&gt;"vibrancy"&lt;/em&gt;&amp;nbsp;– it blends the color of the text with the material background behind it in a specific way that makes it more readable&lt;/p&gt;
&lt;p&gt;To apply the vibrancy effect, use the new &lt;code&gt;.foregroundStyle&lt;/code&gt; modifier instead of &lt;code&gt;.foregroundColor&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;HStack {
    Text("\(stops.count) colors")
        .foregroundStyle(.secondary)
}
.background(.regularMaterial)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can use &lt;code&gt;.foregroundStyle&lt;/code&gt; with hierarchical styles like &lt;code&gt;.secondary&lt;/code&gt; and with actual colors (or even gradients), and you can even use both together, in which case SwiftUI applies some alpha effect to the selected color:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;VStack {
    Text("Primary").foregroundStyle(.primary)
    Text("Secondary").foregroundStyle(.secondary)
    Text("Tertiary").foregroundStyle(.tertiary)
    Text("Quaternary").foregroundStyle(.quaternary)
}
.foregroundStyle(.purple)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use &lt;code&gt;.foregroundColor&lt;/code&gt; if you want to opt out of vibrancy for a specific view and use an unmodified color&lt;/p&gt;
&lt;p&gt;What's more, a &lt;code&gt;Text&lt;/code&gt; can even present an attributed string that has multiple different colors applied to different ranges and use a vibrancy effect for the parts that don't have a specific color set (it can only have one single &lt;code&gt;foregroundStyle&lt;/code&gt; though)&lt;/p&gt;
&lt;p&gt;You can embed one &lt;code&gt;Text&lt;/code&gt; inside another to construct a &lt;code&gt;Text&lt;/code&gt; with multiple colors in different ranges:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Text("\(stops.count) \(Text("colors").foregroundColor(.red))")
    .foregroundStyle(.secondary)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;It's ok to use &lt;code&gt;.foregroundStyle&lt;/code&gt; on non-material backgrounds&amp;nbsp;– the vibrancy effect is automatically disabled unless there is an appropriate background behind the text&lt;/p&gt;
&lt;p&gt;Text views also automatically disable vibrancy for any embedded emoji&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can read more about vibrancy and materials in my old blog post about the introduction of dark mode in macOS Mojave&amp;nbsp;– "&lt;a href="/2018/07/04/dark-side-mac-1/"&gt;Dark Side of the Mac: Appearance &amp;amp; Materials&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Safe area inset&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is a new view modifier &lt;code&gt;.safeAreaInset&lt;/code&gt; that allows you to position a view on top of a &lt;code&gt;ScrollView&lt;/code&gt; in a way that adjusts the scroll view's top/bottom content insets so that all the content can be reached and the beginning/end isn't obscured by the overlay&lt;/p&gt;
&lt;p&gt;This is especially useful for views using the blur material background, since you want them to be z-stacked with some content on a layer below to show a hint of the colors behind it, but you want all the content in the view below to be accessible by scrolling it above the overlay&lt;/p&gt;
&lt;p&gt;To position a view this way, put the whole overlay view inside the &lt;code&gt;.safeAreaInset&lt;/code&gt; closure:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;List {
    ...
}
.safeAreaInset(edge: .bottom) {
    HStack {
        Text("New gradient")
        Spacer()
        Text("\(stops.count) colors")
            .foregroundStyle(.secondary)    
    }
    .padding()
    .background(.thinMaterial)
}&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Canvas&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;When drawing a large number of graphical elements in a shared area, we can use a &lt;code&gt;ZStack&lt;/code&gt; and the &lt;code&gt;.drawingGroup&lt;/code&gt; modifier:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var body: some View {
    GeometryReader { proxy in
        ZStack {
            ForEach(state.entries.indices, id: \.self) { index in
                let entry = state.entries[index]
                entry.symbol
                    .scaleEffect(...)
                    .position(...)
                    .onTapGesture {
                        withAnimation { ... }
                    }
            }
        }
    }
    .drawingGroup()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;A &lt;code&gt;.drawingGroup&lt;/code&gt; tells SwiftUI to combine all the views it contains on a single layer to improve performance&lt;/p&gt;
&lt;p&gt;This can be used for graphical elements like images, but not for UI controls like text fields or lists&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Using &lt;code&gt;.drawingGroup&lt;/code&gt; still allows you to do things like setting accessibility properties or applying gestures to each element individually&lt;/p&gt;
&lt;p&gt;However, keeping each view's separate identity adds a bit of overhead&lt;/p&gt;
&lt;p&gt;When you draw a really large amount of elements and you need all the performance you can get, you can now use the new &lt;code&gt;Canvas&lt;/code&gt; element&lt;/p&gt;
&lt;p class="arrow"&gt;→ the tradeoff is that you can't attach gestures to individual drawings anymore&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A &lt;code&gt;Canvas&lt;/code&gt; is given a closure that's run every time the canvas is redrawn&lt;/p&gt;
&lt;p&gt;This is a normal, imperative code closure, not a view builder&lt;/p&gt;
&lt;p&gt;It works similarly to &lt;code&gt;drawRect&lt;/code&gt; in AppKit or UIKit&amp;nbsp;– the canvas gives you a context object that you can run some draw commands on:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Canvas { context, size in
    let image = Image(systemName: "sparkle")

    for i in 0..&amp;lt;10 {
        context.draw(image, at: CGPoint(
            x: 0.5 * size.width + Double(i) * 10,
            y: 0.5 * size.height
        ))
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;When drawing an image, the context needs to "resolve" it to get the actual data it can draw, so if you use the same image multiple times in the canvas, you can resolve it manually up front just once&lt;/p&gt;
&lt;p&gt;This also lets you access information like the image size:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Canvas { context, size in
    let image = context.resolve(Image(systemName: "sparkle"))
    let imageSize = image.size

    for i in 0..&amp;lt;10 {
        context.draw(image, at: CGPoint(
            x: 0.5 * size.width + Double(i) * imageSize.width,
            y: 0.5 * size.height
        ))
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To draw shapes in the canvas, use &lt;code&gt;context.fill()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can draw e.g. bezier curves or paths derived from standard SwiftUI shapes&lt;/p&gt;
&lt;p&gt;You can also use standard SwiftUI color objects:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Canvas { context, size in
    let image = context.resolve(Image(systemName: "sparkle"))
    let imageSize = image.size

    for i in 0..&amp;lt;10 {
        let frame = CGRect(
            x: 0.5 * size.width + Double(i) * imageSize.width,
            y: 0.5 * size.height,
            width: imageSize.width,
            height: imageSize.height
        )

        context.fill(
            Ellipse().path(in: frame),
            with: .color(.cyan)
        )
        context.draw(image, in: frame)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To change some properties globally on the context and revert them later, you can create a copy of the context on which you modify the settings, draw some elements using the new context, and the original context will not be affected:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Canvas { context, size in
    let image = context.resolve(Image(systemName: "sparkle"))
    let imageSize = image.size

    for i in 0..&amp;lt;10 {
        let frame = ...

        var innerContext = context
        innerContext.opacity = 0.5
        innerContext.fill(Ellipse().path(in: frame), with: .color(.cyan))

        // drawn with the original opacity
        context.draw(image, in: frame)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can set the color with which a symbol is drawn by setting the &lt;code&gt;shading&lt;/code&gt; on the resolved image:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let image = context.resolve(Image(systemName: "sparkle"))
image.shading = .color(.blue)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;Canvas&lt;/code&gt; is supported on all platforms&amp;nbsp;– it works the same way on iOS, watchOS, tvOS and macOS&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Timeline view&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Timeline view is a new tool that allows you to create animating views by describing exactly how the view should look at a given point in time&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The timeline view requires a &lt;em&gt;"schedule"&lt;/em&gt;&amp;nbsp;– a description of how often it should change&lt;/p&gt;
&lt;p&gt;For drawing an animated canvas, we'll use the &lt;code&gt;.animation&lt;/code&gt; schedule, which redraws the view as often as possible&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;TimelineView&lt;/code&gt; gives you a timeline object from which you can read the current time using the &lt;code&gt;date&lt;/code&gt; property:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;TimelineView(.animation) { timeline in
    Canvas { context, size in
        let now = timeline.date.timeIntervalSinceReferenceDate

        let angle = Angle.degrees(now.remainder(dividingBy: 3) * 120)
        let x = cos(angle.radians)

        var image = context.resolve(Image(systemName: "sparkle"))
        let imageSize = image.size

        for i in 0..&amp;lt;count {
            let frame = CGRect(
                x: 0.5 * size.width
                    + Double(i) * imageSize.width * x,
                y: 0.5 * size.height,
                width: imageSize.width,
                height: imageSize.height
            )

            context.draw(image, in: frame)
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Since there aren't any individual SwiftUI elements inside the &lt;code&gt;Canvas&lt;/code&gt;, you can't specify accessibility information for each element, only for the whole canvas&lt;/p&gt;
&lt;p&gt;If you want to include a more detailed description with separate elements, you can use the new &lt;code&gt;.accessibilityChildren&lt;/code&gt; modifier to provide a completely separate SwiftUI view hierarchy used only for accessibility purposes&lt;/p&gt;
&lt;p&gt;See more in "&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10119/"&gt;SwiftUI accessibility: Beyond the basics&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/whats-new-in-foundation/</id>
    <title>What's new in Foundation</title>
    <published>2022-06-04T21:54:43+02:00</published>
    <updated>2022-06-04T21:54:43+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/whats-new-in-foundation/"/>
    <content type="html">&lt;h3&gt;Attributed strings&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;An attributed string allows you to associate attributes in the form of key-value pairs with specific ranges of the string, in order to make a part of the text use a different style&lt;/p&gt;
&lt;p&gt;A part of a string can have multiple attributes applied to it and the attribute ranges can overlap&lt;/p&gt;
&lt;p&gt;Attributed strings are usually used with APIs that support rich text, like &lt;code&gt;UITextView/NSTextView&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;String attributes are defined in the SDK, but you can also add your own&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Attributed strings in Foundation previously used an ObjC-based reference type called &lt;code&gt;NSAttributedString&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This year, there is a completely new Swift-only struct type called &lt;code&gt;AttributedString&lt;/code&gt;, which takes full advantage of Swift features&lt;/p&gt;
&lt;p&gt;The Swift &lt;code&gt;AttributedString&lt;/code&gt; is a value type, compatible with &lt;code&gt;String&lt;/code&gt;, fully localizable and with a more type-safe API&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p class="help"&gt;ℹ️ I've replaced some of the code samples in this section to better demonstrate the use of the API in this form&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Assigning attributes to strings:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To assign attributes to the whole string, simply assign an appropriate value (which is type-checked at compile time) to a property like &lt;code&gt;font&lt;/code&gt; or &lt;code&gt;foregroundColor&lt;/code&gt; on the attributed string object:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var thanks = AttributedString("Thank you!")
thanks.font = .body.bold()

var website = AttributedString("Please visit our website.")
website.font = .body.italic()
website.link = URL(string: "http://www.example.com")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To assign attributes to a range, use slicing:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let msg = AttributedString()
let start = msg.startIndex
let after5 = msg.index(start, offsetByCharacters: 5)

msg[start ..&amp;lt; after5].foregroundColor = .blue&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also find a range by looking for a substring in the text:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var website = AttributedString("Please visit our website.")

if let range = message.range(of: "visit") {
    message[range].font = .body.italic().bold()
    message.characters.replaceSubrange(range, with: "surf")
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;It may also be useful to hold a set of attributes separately from the string itself and then apply it to one or more strings at some point&amp;nbsp;– for that you can use an &lt;code&gt;AttributeContainer&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var container = AttributeContainer()
container.foregroundColor = .red
container.underlineColor = .primary

website.mergeAttributes(container)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Iterating over an attributed string:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To iterate over the string's contents, you can use one of the provided "views" into the string:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;characters, which provides a collection of separate characters&lt;/li&gt;
&lt;li&gt;runs, which lists ranges of text, each having a single uniform set of attributes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These views are Swift collections, so you can use any standard collection methods on them&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Here, we iterate over the collection of characters in a string, modifying the attributes of some of them:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let characters = message.characters

for i in characters.indices where characters[i].isPunctuation {
    message[i ..&amp;lt; characters.index(after: i)].foregroundColor = .orange
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Now, assuming we have an attributed string like this:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var line1 = AttributedString("Thank you! ")
line1.font = .boldSystemFont(ofSize: 10)

var line2 = AttributedString("Please visit our website")

// make the word "website" a link
let range = line2.range(of: "website")!
line2[range].link = URL(string: "http://example.com")

let message = line1 + line2&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;If we iterate over the string's runs collection like this, we get 3 items, because there are three distinct sections in the string:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let parts = message.runs.map { run in
    String(message.characters[run.range])
}

// =&amp;gt; ["Thank you! ", "Please visit our ", "website"]&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;However, we can also divide the string into runs only by looking at one specific attribute, e.g. &lt;code&gt;.link&lt;/code&gt;&amp;nbsp;– in this case, there will be only two separate runs:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let parts = message.runs[\.link].map { (value, range) in
    String(message.characters[range])
}

// =&amp;gt; ["Thank you! Please visit our ", "website"]&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The value provided for each one will be of &lt;code&gt;URL?&lt;/code&gt; type in this case&amp;nbsp;– the first run will have a value of &lt;code&gt;nil&lt;/code&gt;, and the second one will have the URL we've assigned earlier:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;for (value, range) in message.runs[\.link] {
    if let url = value {
        print(url.host!)    // =&amp;gt; "example.com"
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Attributed string localization:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Attributed strings are fully localizable&lt;/p&gt;
&lt;p&gt;The old ObjC-based &lt;code&gt;NSAttributedString&lt;/code&gt; now also has localization support added&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Attributed string localizations are listed in &lt;code&gt;.strings&lt;/code&gt; files, just like for normal strings&lt;/p&gt;
&lt;p&gt;Both plain strings and attributed strings can now be localized in Swift code using String interpolation, in a similar way to how it's done in SwiftUI:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func prompt(for document: String) -&amp;gt; AttributedString {
    AttributedString(localized: "Would you like to save the document '\(document)'?")
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Xcode can now collect localizable strings for the &lt;code&gt;.strings&lt;/code&gt; files from such initializers in your code using the Swift compiler&lt;/p&gt;
&lt;p&gt;You can turn this on with the build setting: "Localization &amp;gt; Use Compiler to Extract Swift Strings"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Markdown support:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Attributed strings now automatically parse Markdown content and generate attributes from tags like &lt;code&gt;**bold**&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;struct ReceiptView: View {
    var body: some View {
        VStack {
            Text("**Thank you!**")
            Text("_Please visit [our website](https://example.com)._")
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Conversion and archiving:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The new struct-based attributed strings can be easily converted to and from the old ObjC-based reference types:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let nsString = NSAttributedString(message)
let swiftString = AttributedString(nsString)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;AttributedString&lt;/code&gt; conforms to &lt;code&gt;Codable&lt;/code&gt;, so you can encode a string with all its attributes into whatever form that &lt;code&gt;Codable&lt;/code&gt; supports&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Custom attributes:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Apart from the built-in string attributes provided by AppKit, UIKit, SwiftUI and other system frameworks, you can also define your own attributes&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A string attribute consists of a key and a value&lt;/p&gt;
&lt;p&gt;The key is a type the conforms to the &lt;code&gt;AttributedStringKey&lt;/code&gt; protocol&lt;/p&gt;
&lt;p&gt;It defines the type used for the value (through the associated type &lt;code&gt;Value&lt;/code&gt;) and the name used for archiving (&lt;code&gt;static var name&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;The key can also customize encoding and decoding by conforming to some other protocols&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Here, we define a "rainbow" attribute which colors the range of the text with rainbow colors, whose value is one of the three enum cases that set the intensity:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;enum RainbowAttribute: AttributedStringKey {
    enum Value: String {
        case plain
        case fun
        case extreme
    }

    public static var name = "rainbow"
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;If we want the attribute to be encoded &amp;amp; decoded with the string, it needs to be &lt;code&gt;Codable&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;enum RainbowAttribute: CodableAttributedStringKey {
    enum Value: String, Codable {
        case plain
        case fun
        case extreme
    }

    public static var name = "rainbow"
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also make the custom attribute available to the Markdown parser, using a special custom attribute syntax&lt;/p&gt;
&lt;p&gt;To use an attribute in Markdown, it needs to also conform to &lt;code&gt;MarkdownDecodableAttributedStringKey&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The attribute is added to a range of text using a similar syntax like images and URLs:&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;This text contains ^[an attribute](rainbow: 'extreme').

This text contains ^[two attributes](rainbow: 'extreme', otherValue: 42).

This text contains ^[an attribute with 2 properties](someStuff: {key: true, key2: false}).&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The part in the square brackets is the marked text, and the part in parentheses are the attributes&lt;/p&gt;
&lt;p&gt;The attribute values are written as a &lt;a href="https://json5.org"&gt;JSON5&lt;/a&gt; object&lt;/p&gt;
&lt;p&gt;Support for JSON5 parsing was also added to existing JSON parsing APIs like &lt;code&gt;JSONSerialization&lt;/code&gt; and &lt;code&gt;JSONDecoder&lt;/code&gt; (you need to enable a proper option first):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let json5text = "{ foo: 'bar' }"
let json5data = json5text.data(using: .utf8)!

let decoded = JSONSerialization.jsonObject(with: json5data, options: .json5Allowed)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The names of attributes from the attributes JSON object are looked up in &lt;em&gt;"attribute scopes"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;An attribute scope defines a list of attributes from one domain, e.g. SwiftUI or your app&lt;/p&gt;
&lt;p&gt;When creating an &lt;code&gt;AttributedString&lt;/code&gt; from Markdown, you need to specify one single attribute scope from which attributes will be looked up&lt;/p&gt;
&lt;p&gt;However, attribute scopes can be nested in one another, so you can include e.g. a scope of all SwiftUI attributes inside your scope (which in turn includes Foundation attributes)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;An attribute scope is defined as a type conforming to &lt;code&gt;AttributeScope&lt;/code&gt;, nested inside the &lt;code&gt;AttributeScopes&lt;/code&gt; namespace:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;extension AttributeScopes {

    // attribute scope for our "Caffe" app

    struct CaffeAppAttributes: AttributeScope {
        // this is our "rainbow" attribute
        let rainbow: RainbowAttribute

        // here, we include standard SwiftUI attributes
        let swiftUI: SwiftUIAttributes
    }

    // expose our attribute scope on a keypath in AttributeScopes
    var caffeApp: CaffeAppAttributes.Type { CaffeAppAttributes.self }
}

// now, pass the keypath to our scope when creating a string:

let header = AttributedString(
    localized: "^[Fast &amp; Delicious](rainbow: 'extreme') Food",
    including: \.caffeApp
)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To actually render a text marked with custom attributes in a specific way, you need to create a custom view that takes an &lt;code&gt;AttributedString&lt;/code&gt; with those attributes and displays it&amp;nbsp;– see the example &lt;code&gt;RainbowText&lt;/code&gt; SwiftUI view in the "&lt;a href="https://developer.apple.com/documentation/foundation/data_formatting/building_a_localized_food-ordering_app"&gt;Building a Localized Food-Ordering App&lt;/a&gt;&lt;/code&gt; sample code project&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;New formatter APIs&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Formatters are used for converting values like dates or numbers into localized and user-presentable strings&lt;/p&gt;
&lt;p&gt;Current formatters (&lt;code&gt;NSFormatter&lt;/code&gt;) are quite heavy objects, so it's a common pattern to create them once, cache them and reuse them, often across different parts of the app, which isn't always convenient&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This year, there is a completely new API for formatters in Swift, which is aimed to improve both their usability and efficiency&lt;/p&gt;
&lt;p&gt;The formatting is now done by calling a format method on the formatted value directly&lt;/p&gt;
&lt;p&gt;Instead of formatting a date with a date formatter:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .medium

...

dateLabel.text = dateFormatter.string(from: quakeTime)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;We ask the date for a formatted representation:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;dateLabel.text = quakeTime.formatted(date: .abbreviated, time: .standard)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Formatting floating point numbers, before:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;magnitudeLabel.text = String(format: "%.1f", magnitude)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;after:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;magnitudeLabel.text = magnitude.formatted(.number.precision(.fractionLength(1)))&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This is not only more readable, but also safer&amp;nbsp;– the old version requires using string format specifiers that have to be written in a specific way, aren't checked by the compiler and can't be easily remembered, and if the value is not a floating point number&amp;nbsp;– you will get a wrong output instead of a compiler error&lt;/p&gt;
&lt;p&gt;You also now get autocompletion in Xcode for the formatting options&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are new formatting API equivalents for all existing formatters in Foundation&amp;nbsp;– for lists, date components and intervals, measurements, data sizes etc.&lt;/p&gt;
&lt;p&gt;They're designed to help you avoid some common pitfalls when using string-based format specifiers, like using a wrong date format field that only fails in some specific cases&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Formatting dates:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Simplest version:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let formatted = date.formatted()
// =&amp;gt; "6/7/2021, 9:42 AM"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Configure time and date styles:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let onlyDate = date.formatted(date: .numeric, time: .omitted)
// =&amp;gt; "6/7/2021"

let onlyTime = date.formatted(date: .omitted, time: .shortened)
// =&amp;gt; "9:42 AM"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;List specific fields to include:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let formatted = date.formatted(.dateTime.year().day().month())
// =&amp;gt; "Jun 7, 2021"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Customize field styles:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let formattedWide = date.formatted(.dateTime.year().day().month(.wide))
// =&amp;gt; "June 7, 2021"

let weekday = date.formatted(.dateTime.weekday(.wide))
// =&amp;gt; "Monday"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Standardized formats:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let logFormat = date.formatted(.iso8601)
// =&amp;gt; "20210607T164200Z"

let fileNameFormat =
    date.formatted(.iso8601.year().month().day().dateSeparator(.dash))
// =&amp;gt; "2021-06-07"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Setting a specific locale:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let formatted = date.formatted(.dateTime.locale(myLocale))&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The general pattern is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;start with the value to be formatted&lt;/li&gt;
&lt;li&gt;call one of the &lt;code&gt;formatted&lt;/code&gt; methods&lt;/li&gt;
&lt;li&gt;pass a style in the argument (e.g. for dates it's &lt;code&gt;.dateTime&lt;/code&gt; or &lt;code&gt;.iso8601&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;call methods on the format to customize it&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The order of the fields in the chained call doesn't matter&amp;nbsp;– it just lists the fields that should be included somewhere in the final output, and the formatter decides on the order based on the locale&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Formatting date ranges:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let range = (now..&amp;lt;later).formatted()
// =&amp;gt; "6/7/21, 9:42 - 11:05 AM"

let noDate = (now..&amp;lt;later).formatted(date: .omitted, time: .complete)
// =&amp;gt; "9:42:00 AM PDT - 11:05:20 AM PDT"

let timeDuration = (now..&amp;lt;later).formatted(.timeDuration)
// =&amp;gt; "1:23:20"

let components = (now..&amp;lt;later).formatted(.components(style: .wide))
// =&amp;gt; "1 hour, 23 minutes, 20 seconds"

let relative = later.formatted(.relative(presentation: .named, unitsStyle: .wide))
// =&amp;gt; "in 1 hour"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Formatting values as attributed strings:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can ask a formatter to create an attributed string instead by calling &lt;code&gt;.attributed()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The generated attributed string does not have any visual styles applied to it, but it marks all ranges of text that contain specific fields like month or year with formatter-specific properties&lt;/p&gt;
&lt;p&gt;You can then analyze the returned &lt;code&gt;AttributedString&lt;/code&gt; and use these format properties to mark different tokens within the generated string with different colors or text styles&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var dateString: AttributedString {
    var str = date.formatted(.dateTime
                .minute()
                .hour()
                .weekday()
                .locale(locale)
                .attributed())

    // alternative way to create an AttributeContainer
    let weekday = AttributeContainer.dateField(.weekday)
    let color = AttributeContainer.foregroundColor(.orange)

    // we can ask an AttributedString to replace the attributes
    // in one container set with those in another container

    // here, the text range marked with a "weekday" attribute
    // will have an orange text color applied to it
    str.replaceAttributes(weekday, with: color)

    return str
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Parsing dates from strings:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To convert the values the other way, you can use a new &lt;code&gt;Date&lt;/code&gt; initializer that takes a "strategy" argument&lt;/p&gt;
&lt;p&gt;The strategy is an object that tells the parser what kind of fields to expect in the input&lt;/p&gt;
&lt;p&gt;You can use one of the format objects shown earlier as a parsing strategy:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let format = Date.FormatStyle().year().day().month()
let formatted = date.formatted(format)
// formatted is "Jun 7, 2021"

if let date = try? Date(formatted, strategy: format) {
  // date is 2021-06-07 07:00:00 +0000
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also use a more specialized strategy object, like this one for parsing dates sent from the server in a specific strict format:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let strategy = Date.ParseStrategy(
    format: "\(year: .defaultDigits)-\(month: .twoDigits)-\(day: .twoDigits)",
    timeZone: TimeZone.current
)

if let date = Date("2021-06-07", strategy: strategy) {
    // date is 2021-06-07 07:00:00 +0000
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Instead of using magic string format specifiers, &lt;code&gt;ParseStrategy&lt;/code&gt; lets you specify the fields using string interpolation&amp;nbsp;– checking the format at compile time, and helping you in Xcode with autocomplete and inline documentation&lt;/p&gt;
&lt;p&gt;&lt;em&gt;"No more guessing how many Y characters you should use to parse a year"&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Formatting numbers:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let value = 12345

var formatted = value.formatted()
// =&amp;gt; "12,345"

let percent = 25
let percentFormatted = percent.formatted(.percent)
// =&amp;gt; "25%"

let scientific = 42e9
let sciFormatted = scientific.formatted(.number.notation(.scientific))
// =&amp;gt; "4.2E10"

let price = 29
let priceFormatted = price.formatted(.currency(code: "usd"))
// =&amp;gt; "$29.00"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Formatting lists:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To format a list of things, call &lt;code&gt;.formatted()&lt;/code&gt; on an array and specify which of the formats should be used for each member:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let list = [25, 50, 75].formatted(.list(memberStyle: .percent, type: .or))
// =&amp;gt; "25%, 50%, or 75%"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Formatting text field content in SwiftUI:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In SwiftUI, you can assign a format to a text field&lt;/p&gt;
&lt;p&gt;The text field will automatically parse and reformat the value entered by the user, and if a correct value of a given type can be parsed from the input, it will assign it through the binding to the connected state property as e.g. a number or a date value:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;struct ReceiptTipView: View {
    @State var tip = 0.15

    var body: some View {
        HStack {
            Text("Tip")
            Spacer()
            TextField("Amount",
                value: $tip,
                format: .percent)
        }
    }
}&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Automatic grammar agreement&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;In some languages, like in Spanish, there needs to be a grammatical agreement between the words in a sentence, e.g. in a phrase "two small salads" the number, the noun and the adjective need to have not only the same pluralization, but also same grammatical gender&lt;/p&gt;
&lt;p&gt;This makes it often extremely complex to provide full and correct localization for all combinations of UI strings, and either makes the localization process more time-consuming, or makes the translation less correct or less natural&lt;/p&gt;
&lt;p&gt;E.g. instead of just translating "small", "large", "juice" and "salad" you need each combination of "small juice", "large salad" etc. (and then add another dimension for pluralizations)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Foundation in iOS 15 adds a new feature called "automatic grammar agreement" that handles a lot of these problems automatically (available in English and Spanish at the moment)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now write a string like this:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Text("Add ^[\(quantity) \(size) \(food)](inflect: true) to your order")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The custom attribute Markdown syntax for Attributed Strings is used to mark a part of the text that needs to be automatically inflected with the "inflect" attribute&lt;/p&gt;
&lt;p&gt;This will be exported to an English strings file like this:&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;"Add ^[%lld %@ %@](inflect: true) to your order" =
    "Add ^ [%lld %@ %@](inflect: true) to your order";

"Pizza" = "Pizza";
"Juice" = "Juice";
"Salad" = "Salad";

"Small" = "Small";
"Large" = "Large";&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;And to a Latin American Spanish strings file like this:&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;/* in Spanish the order of the noun and adjective is reversed */

"Add ^[%lld %@ %@](inflect: true) to your order" =
    "Añadir ^[%1$lld %3$@ %2$@](inflect: true) a tu pedido";

"Pizza" = "Pizza";
"Juice" = "Jugo";
"Salad" = "Ensalada";

"Small" = "Pequeño";
"Large" = "Grande";&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The key change is that you don't need to provide separate translations for e.g. "pequeño" (masculine) and "pequeña" (feminine)&amp;nbsp;– the grammar agreement engine handles this automatically&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;User's term of address:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In some languages some of the localized text also needs to change depending on the person reading it, because it needs to be adjusted for the person's term of address (depending on the gender)&lt;/p&gt;
&lt;p&gt;Now, users using supported languages (currently Spanish) can specify their term of address in the iOS Settings app, under "Language &amp;amp; Region", and can choose to share it with apps&lt;/p&gt;
&lt;p&gt;This is done the same way as in the previous example:&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;^[Bienvenido](inflect: true) a Notas&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Such text can be displayed as "Bienvenido…" or "Bienvenida…" depending on the user's gender&lt;/p&gt;
&lt;p&gt;You can also provide a default text to be used if this information is not available:&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;^[Bienvenido](inflect: true, inflectionAlternative: "Te damos la bienvenida") a Notas&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/discover-concurrency-in-swiftui/</id>
    <title>Discover concurrency in SwiftUI</title>
    <published>2022-06-01T21:24:26+02:00</published>
    <updated>2022-06-01T21:24:26+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/discover-concurrency-in-swiftui/"/>
    <content type="html">&lt;h3&gt;The SwiftUI run loop and Observable Objects&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The SwiftUI engine runs a loop that receives some events, lets you update your models, checks them for changes, recreates necessary view objects and re-renders the parts of the UI that should be updated&lt;/p&gt;
&lt;p&gt;The run loop runs in "ticks" happening in regular intervals, and all of it executes on the main thread / main actor&lt;/p&gt;
&lt;p&gt;The events sent from &lt;code&gt;ObservableObject&lt;/code&gt;'s &lt;code&gt;@Published&lt;/code&gt; also need to be sent on the main actor for this to work correctly&lt;/p&gt;
&lt;p&gt;Let's see exactly why:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;With such update method:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;class Photos: ObservableObject {
    @Published private(set) var items: [SpacePhoto] = []

    func updateItems() {
        let fetched = fetchPhotos()
        items = fetched
    }

    func fetchPhotos() -&amp;gt; [SpacePhoto] { ... }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;the update goes like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;when we assign the &lt;code&gt;items&lt;/code&gt; property, which is &lt;code&gt;@Published&lt;/code&gt;, the &lt;code&gt;Published&lt;/code&gt; wrapper automatically sends an &lt;code&gt;objectWillChange&lt;/code&gt; event that is observed by SwiftUI, before the changes happen&lt;/li&gt;
&lt;li&gt;at that point SwiftUI records a snapshot of the data before it changes&lt;/li&gt;
&lt;li&gt;then, the new value is written to the property&lt;/li&gt;
&lt;li&gt;at the next "tick" of the run loop&amp;nbsp;– which might happen immediately afterwards, or a bit later&amp;nbsp;– SwiftUI compares the current value of the property to the saved snapshot, and this tells it what exactly was changed&lt;/li&gt;
&lt;li&gt;since some of the data was changed, the appropriate parts of the UI are re-rendered&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If all of the above happens on the main thread, these steps are guaranteed to happen in order&lt;/p&gt;
&lt;p&gt;However, if the code takes some time to run, this may cause the run loop to miss the next tick, because the main thread will be busy calculating the data&amp;nbsp;– which will result in dropped frames and a possibly degraded user experience&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;So you may want to run the calculations on a background queue:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func updateItems() {
    DispatchQueue.global().async {
        let fetched = fetchPhotos()
        self.items = fetched
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This way, the main thread isn't blocked, the run loop is free to run the next ticks there until the data is ready&lt;/p&gt;
&lt;p&gt;However, if the assignment (and the resulting notification) happens outside of the main thread, this could mess up the order of operations and could result in the view not being updated&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the long-running operation finishes running on the background thread&lt;/li&gt;
&lt;li&gt;it sends the &lt;code&gt;objectWillChange&lt;/code&gt; event&lt;/li&gt;
&lt;li&gt;SwiftUI takes a snapshot of the data&lt;/li&gt;
&lt;li&gt;*but* because this is all happening on the background thread, the run loop is free to perform another tick on the main thread in the meantime, which it might possibly do at this very moment, right after &lt;code&gt;objectWillChange&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;in this case, SwiftUI compares the snapshot to the current data, but the data hasn't changed yet, so there's nothing to update&lt;/li&gt;
&lt;li&gt;now, the value of the property is updated on the background thread&lt;/li&gt;
&lt;li&gt;but SwiftUI has already compared the snapshots, so it has forgotten about the previous state and will not compare the data again…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To make sure the order of operations is always right:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;1. &lt;code&gt;objectWillChange&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;2. the state changes,&lt;/li&gt;
&lt;li&gt;3. and the run loop runs a tick,&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;all of these need to be done on the same thread&amp;nbsp;– the main thread&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Previously, the solution would have been to jump back to the main thread before saving the data:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func updateItems() {
    DispatchQueue.global().async {
        let fetched = fetchPhotos()
        DispatchQueue.main.async {
            self.items = fetched
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;With Swift 5.5, a better approach is to instead make the fetching an asynchronous operation and use &lt;code&gt;await&lt;/code&gt; to wait for the result&lt;/p&gt;
&lt;p&gt;By using &lt;code&gt;await&lt;/code&gt; we "yield" the current (main) thread instead of blocking it and it's free to perform next run loop ticks in the meantime, and when the response is received, the method continues execution&amp;nbsp;– on the same thread where it started, automatically:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func updateItems() async {
    let fetched = await fetchPhotos()
    items = fetched
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To make sure that all of the updates to properties in the &lt;code&gt;Photos&lt;/code&gt; class happen on the main thread and that we don't make a mistake somewhere, we can mark the class with the global actor &lt;code&gt;@MainActor&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@MainActor
class Photos: ObservableObject {
    @Published private(set) var items: [SpacePhoto] = []

    ...
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This way the Swift compiler guarantees that the code in this class runs on the main thread, at compile time&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Associating async tasks with views&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;SwiftUI provides a new &lt;code&gt;.task&lt;/code&gt; modifier that can be used to associate an asynchronous task with a given view&lt;/p&gt;
&lt;p&gt;The task is run at the beginning of the view's lifetime, like &lt;code&gt;.onAppear&lt;/code&gt;, but the closure your provide to it is asynchronous, which makes it easier to make asynchronous calls with &lt;code&gt;await&lt;/code&gt; inside&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In this app, we can use the &lt;code&gt;.task&lt;/code&gt; modifier to run the method which prepares the photo data in the model that we've written above:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;struct CatalogView: View {
    @StateObject private var photos = Photos()

    var body: some View {
        NavigationView {
            List {
                ...
            }
        }
        .task {
            await photos.updateItems()
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;What's more, the task's lifetime is tied to the view's lifetime, so the task is also automatically cancelled when the view is removed from the UI&lt;/p&gt;
&lt;p&gt;You can use this to e.g. create a task that continuously reads data from an async sequence for the whole duration of the view's lifetime&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Async images&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;SwiftUI now includes an &lt;code&gt;AsyncImage()&lt;/code&gt; view that automatically loads the contents of the image for you from a given URL:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;AsyncImage(url: photo.url)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can customize both the look of the resulting image and the placeholder that is displayed while the image is loading:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;AsyncImage(url: photo.url) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fill)
} placeholder: {
    ProgressView()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also customize the error handling&amp;nbsp;– for that, check out the &lt;code&gt;AsyncImage(url:scale:transaction:content:)&lt;/code&gt; initializer of this view&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Running async code from SwiftUI action handlers&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;To call asynchronous code from inside a button handler closure, which is synchronous, use a &lt;code&gt;Task&lt;/code&gt; block to execute a piece of asynchronous code in synchronous context:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;struct SavePhotoButton: View {
    var photo: SpacePhoto
    @State private var isSaving = false

    var body: some View {
        Button {
            Task {
                isSaving = true
                await photo.save()
                isSaving = false
            }
        } label: {
            Text("Save")
                .opacity(isSaving ? 0 : 1)
                .overlay {
                    if isSaving {
                        ProgressView()
                    }
                }
        }
        .disabled(isSaving)
        .buttonStyle(.bordered)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;💡 Tip: to show a spinner inside a button like this, use &lt;code&gt;.opacity()&lt;/code&gt; to hide its label while the spinner is displayed&amp;nbsp;– since the label is still there, just with 0 opacity, it makes sure that the button stays at the same size in the loading state&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p class="help"&gt;ℹ️ In the talk, the asynchronous code was instead wrapped in a call to an &lt;code&gt;async {}&lt;/code&gt; function, which was later replaced with the &lt;code&gt;Task&lt;/code&gt; initializer&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Reloading the data using pull-to-refresh&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;SwiftUI adds a new &lt;code&gt;.refreshable&lt;/code&gt; modifier this year which automatically implements a pull-to-refresh behavior in your view, where scrolling the contents of the view beyond the top edge triggers a reload of the data&lt;/p&gt;
&lt;p&gt;Inside the closure passed to &lt;code&gt;.refreshable&lt;/code&gt;, provide the reloading code that should be run on refresh&amp;nbsp;– the code can be asynchronous, so it can use &lt;code&gt;await&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;List {
    ForEach(photos.items) { item in
        ...
    }
}
.refreshable {
    await photos.updateItems()
}&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/customize-and-resize-sheets-uikit/</id>
    <title>Customize and resize sheets in UIKit</title>
    <published>2022-05-28T21:42:00+02:00</published>
    <updated>2022-05-28T21:42:00+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/customize-and-resize-sheets-uikit/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;iOS 13 introduced a &lt;a href="/notes/wwdc19/modernizing-your-ui-for-ios13/"&gt;refined appearance for sheets&lt;/a&gt;, which don't cover the full screen anymore on the iPhone, but instead show a curved top edge that can be used to dismiss the sheet by pulling it down&lt;/p&gt;
&lt;p&gt;iOS 15 expands the sheets API adding a lot of new customizations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make vertically resizable sheets that only cover half of the screen&lt;/li&gt;
&lt;li&gt;you can remove the dimming view, creating non-modal UIs where the user can interact with the content behind the sheet&lt;/li&gt;
&lt;li&gt;you can display non-full-screen sheets on iPhones in landscape position&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h3&gt;Creating sheets&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Sheet appearance is managed through a new presentation controller class, named &lt;code&gt;UISheetPresentationController&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You don't create a sheet controller instance yourself, but instead get it from the view controller managing the modal screen that you want to present:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;if let sheet = viewController.sheetPresentationController {
    // customize the sheet...
}

present(viewController, animated: true)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;sheetPresentationController&lt;/code&gt; property will always return a non-nil value as long as the view controller's &lt;code&gt;modalPresentationStyle&lt;/code&gt; is &lt;code&gt;formSheet&lt;/code&gt; or &lt;code&gt;pageSheet&lt;/code&gt; (which it is by default unless you override it)&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Detents&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;A sheet can be configured with a list of so-called &lt;em&gt;"detents"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Detents are positions at which the end of the sheet can rest after it's opened or resized&lt;/p&gt;
&lt;p&gt;There are currently two system-defined detents available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;large&lt;/code&gt;, which is the normal full size of a sheet, as in previous versions of iOS (the sheet going up almost to the top of the screen on the iPhone)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;medium&lt;/code&gt;, which is about half the standard height (the sheet filling bottom half of the screen on the iPhone)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can set the &lt;code&gt;detents&lt;/code&gt; property of a sheet to either or both of these:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;// standard full-height sheet - the default value
sheet.detents = [.large()]

// a sheet that can be switched between half-height and full-height
// the order matters - the first detent is the initial position
sheet.detents = [.medium(), .large()]

// a sheet that is medium height and cannot be made larger
sheet.detents = [.medium()]&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This can even be used on system modal views that normally expect to be presented in a full-height sheet, like the &lt;code&gt;PHPhotoPicker&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func showImagePicker()
    let picker = PHPickerViewController()
    picker.delegate = self

    if let sheet = picker.sheetPresentationController {
        sheet.detents = [.medium(), .large()]
    }

    present(picker, animated: true)
}

func picker(_ picker: PHPickerViewController,
    didFinishPicking results: [PHPickerResult]) {

    // assign result to imageView.image
    // do not dismiss the picker
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;calling the first function shows a system photo picker in a half-height sheet, in the bottom half of the screen&lt;/li&gt;
&lt;li&gt;the top half of the screen shows the parent screen behind it, which displays the previously selected photo&lt;/li&gt;
&lt;li&gt;when a photo is selected, we display it in the parent view, but we do not dismiss the picker sheet, allowing the user to quickly test different photos&lt;/li&gt;
&lt;li&gt;the sheet can be closed by pressing the Cancel button inside the sheet or by dragging the sheet down&lt;/li&gt;
&lt;li&gt;the sheet can also be resized by the user by dragging it up or down, switching between half height and full height&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The sheet can also be resized by scrolling the scrollable contents inside the sheet, e.g. the grid of photos in the picker in this case&lt;/p&gt;
&lt;p&gt;This is normally useful, but it might not be what you want&lt;/p&gt;
&lt;p&gt;To disable this behavior, turn off this option in the sheet:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;sheet.prefersScrollingExpandsWhenScrolledToEdge = false&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In the example above, if we do not dismiss the dialog when the user makes a selection, it will work fine in "medium" half-height mode, but in the "large" full-height mode it might seem like the app is broken, because the user will not see the view behind the dialog&lt;/p&gt;
&lt;p&gt;In such cases, it makes sense to programmatically adjust the detent position&lt;/p&gt;
&lt;p&gt;You can do that by setting &lt;code&gt;selectedDetentIdentifier&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func picker(_ picker: PHPickerViewController,
    didFinishPicking results: [PHPickerResult]) {

    // assign result to imageView.image

    if let sheet = picker.sheetPresentationController {
        sheet.animateChanges {
            sheet.selectedDetentIdentifier = .medium
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;sheet.animateChanges&lt;/code&gt; does exactly what you'd expect&amp;nbsp;– if you want to change the detent position instantly, without animation, then just set the property without using an &lt;code&gt;animateChanges&lt;/code&gt; block&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Sheets also automatically adjust when the software keyboard is shown or hidden&amp;nbsp;– if the sheet is in half-height mode and the keyboard slides up, it will automatically move to the top of the screen, and back to the medium position when the keyboard goes away&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Removing dimming overlay:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can choose to hide the overlay covering the view behind the sheet&lt;/p&gt;
&lt;p&gt;This makes the back view more visible, and also makes it possible to interact with it while the sheet is open, making the sheet non-modal&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To hide the overlay, you set the largest detent level at which the dimming overlay should be hidden; at detent levels higher than this, the overlay will be visible&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;sheet.largestUndimmedDetentIdentifier = .medium&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setting it to &lt;code&gt;.medium&lt;/code&gt; means that &lt;code&gt;.medium&lt;/code&gt; is the largest detent with no overlay, and &lt;code&gt;.large&lt;/code&gt; will still show an overlay&lt;/li&gt;
&lt;li&gt;setting it to &lt;code&gt;.large&lt;/code&gt; means that it will show no overlay in either &lt;code&gt;.medium&lt;/code&gt; or &lt;code&gt;.large&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;leaving it at the default value (&lt;code&gt;nil&lt;/code&gt;) means the overlay will be shown in all positions&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p class="help"&gt;ℹ️ In the talk, the property was called &lt;code&gt;smallestUndimmedDetentIdentifier&lt;/code&gt;, which didn't really make sense&amp;nbsp;– the name was changed in one of the betas&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Sheets in horizontal mode&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;When the iPhone is in horizontal orientation, presented view controllers are normally displayed in full screen mode, without the cut-off rounded edge at the top and covering full width of the device&lt;/p&gt;
&lt;p&gt;You can now request to show a modal view as a sheet also in horizontal position by setting:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;sheet.prefersEdgeAttachedInCompactHeight = true&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The sheet will by default use the whole available width within in the safe area&lt;/p&gt;
&lt;p&gt;If you want the sheet to have a custom width, you can additionally set:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;sheet.widthFollowsPreferredContentSizeWhenEdgeAttached&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can customize the preferred sheet width using &lt;code&gt;preferredContentSize&lt;/code&gt;, just like on the iPad&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Additional customizations&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can show a "grabber" bar at the top edge of the sheet, like on the bottom sheet in the Maps app, in order to make it more obvious that a sheet can be dragged by moving the top edge:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;sheet.prefersGrabberVisible = true&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also customize the corner radius of the top corners of the sheet:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;sheet.preferredCornerRadius = 20.0&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Note that when the screen behind the sheet is shown in a stacked sheet, it will also use the same corner radius for consistency&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Popovers adapting into sheets&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;It's possible to present the same view as a popover on the iPad and as a sheet on the iPhone and in partial-width modes on the iPad (automatically adjusting between the two as you resize the window)&lt;/p&gt;
&lt;p&gt;To do that, customize the popover presentation controller and use &lt;code&gt;adaptiveSheetPresentationController&lt;/code&gt; to configure its sheet presentation:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func showImagePicker(_ sender: UIBarButtonItem) {
    let picker = PHPickerViewController()
    picker.delegate = self

    // show the view as a popover by default
    picker.modalPresentationStyle = .popover

    // picker.sheetPresentationController is nil
    if let popover = picker.popoverPresentationController {
        popover.barButtonItem = sender

        // access the sheet controller of the popover
        let sheet = popover.adaptiveSheetPresentationController

        sheet.detents = [.medium(), .large()]
        sheet.smallestUndimmedDetentIdentifier = .medium
    }

    present(picker, animated: true)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Note that if &lt;code&gt;modalPresentationStyle&lt;/code&gt; is set to &lt;code&gt;.popover&lt;/code&gt;, the &lt;code&gt;sheetPresentationController&lt;/code&gt; of the presented view will always be nil, so you need to access it through &lt;code&gt;popoverPresentationController.adaptiveSheetPresentationController&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Updating your app&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;How you can update your app for iOS 15:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;review your app for areas that would benefit from medium-height or non-modal sheets&lt;/li&gt;
&lt;li&gt;if you have any custom built half-height sheet views, replace them with built-in sheets&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/improve-access-to-photos/</id>
    <title>Improve access to Photos in your app</title>
    <published>2022-05-28T16:10:23+02:00</published>
    <updated>2022-05-28T16:10:23+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/improve-access-to-photos/"/>
    <content type="html">&lt;h3&gt;Improvements to the Photo Picker&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Privacy:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;When an app doesn't ask for a Photo Library access and only presents the Photo Picker&amp;nbsp;– which runs out of process and automatically has access to the whole library, from which the user can pick some photos for the app&amp;nbsp;– it was possible for people to misinterpret this workflow and assume that the app itself had a full access to the whole photo library, even if it didn't&lt;/p&gt;
&lt;p&gt;So now, in the Settings app, on the Photos Privacy screen, there is a new section for apps that only use the Photos Picker, titled "Apps with one-time photo selection"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Ordered selection:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In some apps, users may need to be able to select a few photos in a specific order (e.g. when adding them to a social media post)&lt;/p&gt;
&lt;p&gt;In iOS 15, your app can configure the picker to show selection order as badges ① ② ③ etc.&lt;/p&gt;
&lt;p&gt;To do that, set the &lt;code&gt;selection&lt;/code&gt; property in the picker configuration to &lt;code&gt;.ordered&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var configuration = PHPickerConfiguration()
configuration.selectionLimit = 0
configuration.selection = .ordered&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Selection adjustment:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now preselect some photos in the picker to display user's previous selection that they can edit, adding additional photos or removing some previously selected ones&lt;/p&gt;
&lt;p&gt;To preselect photos, pass an array of asset identifiers to the picker configuration:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let assetIdentifiers: [String] = previousSelection

var configuration = PHPickerConfiguration(photoLibrary: .shared)
configuration.selectionLimit = 0
configuration.preselectedAssetIdentifiers = assetIdentifiers&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;⚠️ One caveat: when the picker returns updated results, all photos that have been previously selected (and included in the preselection config you provide) will not include the item providers&lt;/p&gt;
&lt;p&gt;So you need to keep the original list of photos with their data until after the updated picker results are returned&lt;/p&gt;
&lt;p&gt;If the preselected picker dialog is cancelled, it will return the preselected assets you've provided with all item providers empty&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In the delegate callback, you need to merge together the old results with the new results, taking the value from the old results for any photo that was selected previously (since there won't be an item provider in the new one):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func picker(
    _ picker: PHPickerViewController,
    didFinishPicking newResults: [PHPickerResult]
) {
    dismiss(animated: true)

    let existingSelection: [String: PHPickerResult] = self.lastSelection
    var newSelection = [String: PHPickerResult]()

    for result in results {
        let identifier = result.assetIdentifier!
        newSelection[identifier] = existingSelection[identifier] ?? result
    }

    self.lastSelection = newSelection

    // do something with selected assets
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Reporting progress:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Some assets that the user selects in the picker may not be immediately available and need to be downloaded from the cloud first, which may take a moment (especially for videos)&amp;nbsp;– this can happen if they're selecting something from their iCloud Photos and the "Optimize Storage" option is turned on&lt;/p&gt;
&lt;p&gt;In that case, previously your app could only show a spinner indicator, since it didn't have access to information about the download progress&lt;/p&gt;
&lt;p&gt;Now, the asset's item provider can give you a &lt;code&gt;Progress&lt;/code&gt; object that you can use to track download progress and show a more appropriate loading UI:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let result: PHPickerResult = ...
let provider: NSItemProvider = result.itemProvider
let identifier: String = UTType.movie.identifier

if provider.hasItemConformingToTypeIdentifier(identifier) {
    let progress: Progress = provider.loadFileRepresentation(
        forTypeIdentifier: identifier
    ) { url, error in
        // Do something with the video, or handle the error
    }

    // Show progress in the meantime
}&lt;/pre&gt;
&lt;h3&gt;New cloud identifier APIs&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are some categories of apps that require full or partial direct access to the user's photo library, e.g. photo editing apps, camera apps or apps whose purpose is to display the photo library in some specific way&lt;/p&gt;
&lt;p&gt;Those apps use the PhotoKit APIs to access and modify the photo library&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;When using those APIs, assets are returned with unique identifiers that your app can save for later and then use again to retrieve the same assets on the next launch&lt;/p&gt;
&lt;p&gt;These identifiers are specific to each device, even if some of the assets are synced between devices using iCloud Photos&lt;/p&gt;
&lt;p&gt;If your app syncs some user-generated data that references user's photos between devices, and you want to access the same photos on multiple devices, you can use the new cloud identifiers that identify the same photo globally&lt;/p&gt;
&lt;p&gt;There is a mapping between local identifiers and cloud identifiers and you can use a cloud identifier to look up a local copy of the photo&lt;/p&gt;
&lt;p&gt;Cloud identifiers work even if the device is not signed into iCloud Photos (or even never was)&lt;/p&gt;
&lt;p&gt;The new identifiers are represented by &lt;code&gt;PHCloudIdentifier&lt;/code&gt; objects&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;How to use cloud identifiers:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;1) Get some local photos from a device's library using CloudKit&lt;/p&gt;
&lt;p&gt;2) Map local identifiers to cloud identifiers:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let cloudMappings = PHPhotoLibrary.shared()
        .cloudIdentifierMappings(forLocalIdentifiers: localIdentifiers)

for (localIdentifier, cloudMapping) in cloudMappings {
    if let cloudIdentifier = cloudMapping.cloudIdentifier {
        // save the cloudIdentifier for later
        resolved[localIdentifier] = cloudIdentifier
    } else {
        // handle the cloudMapping error
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;3) Transfer the cloud identifiers to other devices using whatever communication method you want to use (iCloud / CloudKit etc.)&lt;/p&gt;
&lt;p class="arrow"&gt;→ use &lt;code&gt;stringValue&lt;/code&gt; to encode the identifier to a string&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;4) Look up local identifiers on each device based on the cloud identifiers in the same way:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let localMappings = PHPhotoLibrary.shared()
        .localIdentifierMappings(for: cloudIdentifiers)

for (cloudIdentifier, localMapping) in localMappings {
    if let localIdentifier = localMapping.localIdentifier {
        // add the localIdentifier to our resolved assets
        resolved[cloudIdentifier] = localIdentifier
    } else {
        // handle the localMapping error
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;5) Use the local identifiers to fetch the assets and display them&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Error handling:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The mapping in both directions may return an error instead of the identifier, so you need to be able to handle that case&lt;/p&gt;
&lt;p&gt;There are two possible kinds of errors to take into account:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;1) Identifier Not Found&amp;nbsp;– if the app isn't able to find or access the relevant record:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let error = localMapping.error! as NSError

if error.code == PHPhotosError.identifierNotFound.rawValue {
    // couldn't find this photo, add it to the missing photos list
    missingPhotos.append(cloudIdentifier)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;2) Multiple Identifiers Found&amp;nbsp;– this can happen if the cloud state isn't completely in sync, and the app tries to find the image using content match and finds multiple copies; in this case, the error info will contain the list of matching identifiers under &lt;code&gt;PHLocalIdentifiersErrorKey&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let error = localMapping.error! as NSError

if error.code == PHPhotosError.multipleIdentifiersFound.rawValue {
    // found multiple matches, prompt the user to pick one
    let matches = error.userInfo[PHLocalIdentifiersErrorKey] as! [String]
    multipleMatches[cloudIdentifier] = matches
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Note: looking up cloud identifiers takes some work, so use local identifiers for normal app interactions and map them to cloud identifiers only for syncing with other devices&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Updates to the limited library&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The limited library is when you access the user's photo library using PhotoKit after the user has requested to only share selected photos with your app&lt;/p&gt;
&lt;p&gt;This is designed to work transparently for your app&amp;nbsp;– all PhotoKit APIs work fine, but they work as if the photo library only contained those selected photos and nothing else&lt;/p&gt;
&lt;p&gt;See last year's talk &lt;a href="/notes/wwdc20/handle-the-limited-photos-library/"&gt;Handle the Limited Photos Library in Your App&lt;/a&gt; for more info&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In iOS 15, apps can now create, fetch and update their own photo albums within the user's photo library when running in the limited library mode&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Also, when you call &lt;code&gt;photoLibrary.presentLimitedLibraryPicker()&lt;/code&gt; to let the user adjust a previous selection of photos shared with your app, you can now provide a callback which will be given a list of photos that have just been added to the selection:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let library = PHPhotoLibrary.shared()
library.presentLimitedLibraryPicker(from: controller) { addedIdentifiers in
   // fetch the newly added photos and use them in your app 
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;If your app still uses the old &lt;a href="https://developer.apple.com/documentation/assetslibrary"&gt;Assets Library framework&lt;/a&gt; that was deprecated in iOS 9 (&lt;code&gt;ALAssets*&lt;/code&gt;)&amp;nbsp;– please switch to PhotoKit and the Photos Picker, the old API will be removed in a future SDK&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/craft-search-experiences-in-swiftui/</id>
    <title>Craft search experiences in SwiftUI</title>
    <published>2022-05-17T22:39:39+02:00</published>
    <updated>2022-05-17T22:39:39+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/craft-search-experiences-in-swiftui/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;This year, SwiftUI adds a &lt;code&gt;.searchable&lt;/code&gt; view modifier which adds a search UI appropriate for the current platform&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Here's how it's used in the new Weather app written in SwiftUI:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationView {
    WeatherList(text: $text) {
        ForEach(data) { item in
            WeatherCell(item)
        }
    }
}
.searchable(text: $text)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;What happens behind the scenes is that the &lt;code&gt;.searchable&lt;/code&gt; modifier puts the search field configuration into the Environment, and the views within the hierarchy look for the search field configuration there and apply it in the best way for the given platform&lt;/p&gt;
&lt;p&gt;In this case, the &lt;code&gt;NavigationView&lt;/code&gt; knows how to display the search field and will show it e.g. at the top of one of its panes on iOS, or on the right side of the window toolbar on macOS&lt;/p&gt;
&lt;p&gt;If no view knows how to display the search field, a default rendering is used which puts it in the toolbar&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A search field isn't very useful without another piece of UI that displays the search results, which is something that you need to implement yourself&lt;/p&gt;
&lt;p&gt;The Weather app does it this way:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;struct WeatherList: View {
    @Binding var text: String

    @Environment(\.isSearching) private var isSearching: Bool

    var body: some View {
        WeatherCitiesList()
        .overlay {
            if isSearching &amp;&amp; !text.isEmpty {
                WeatherSearchResults()
            }
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;.searchable&lt;/code&gt; modifier puts an &lt;code&gt;\.isSearching&lt;/code&gt; key in the Environment, which you can check to see if a search field is focused somewhere on the screen, and show a different UI with search results if it is&lt;/li&gt;
&lt;li&gt;the view checks the &lt;code&gt;text&lt;/code&gt; property (which you need to pass yourself) and if it's not empty, displays a list of matching locations&lt;/li&gt;
&lt;li&gt;the results list is displayed as an overlay on top of the standard UI&amp;nbsp;– this is done so that when the user leaves the search field and the results list is hidden, the main UI below remains unchanged (unless they user has picked something from the results)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The second example is the "Colors" app:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationView {
    Sidebar()
    DetailView()
}
.searchable(text: $text)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Here's how we implement search here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;ul&gt;
&lt;li&gt;when you apply &lt;code&gt;.searchable&lt;/code&gt; to a two-pane &lt;code&gt;NavigationView&lt;/code&gt;, it will add the search field to its first pane (the master view); if you want it to be added to the detail view, add the modifier to the detail view directly&lt;/li&gt;
&lt;li&gt;on iOS and iPad OS, the &lt;code&gt;Sidebar&lt;/code&gt; view uses the &lt;code&gt;\.isSearching&lt;/code&gt; Environment key to display search results as an overlay over the list sidebar&lt;/li&gt;
&lt;li&gt;on macOS, &lt;code&gt;NavigationView&lt;/code&gt; puts the search field in the right side of the sidebar, automatically collapsing it into a button if the window is too small&lt;/li&gt;
&lt;li&gt;on the Mac we display the search results instead in the main window pane, on top of the &lt;code&gt;DetailView&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;on tvOS, we use a slightly different layout structure, adding a tab bar with one tab used for the standard navigation and the other for search, and putting the search UI in the second tab above a results list:&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NavigationView {
    TabView {
        Sidebar()
        ColorsSearch()
            .searchable(text: $text)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In most cases, the &lt;code&gt;.searchable&lt;/code&gt; modifier applied to &lt;code&gt;NavigationView&lt;/code&gt; will automatically render the search field in the appropriate place of the hierarchy&lt;/p&gt;
&lt;p&gt;However, you always need to decide how and where to display the search results in a way appropriate for your app (which may vary between platforms)&lt;/p&gt;
&lt;p&gt;In some cases you may want to use a different layout between platforms, as we do here in case of tvOS&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Search suggestions&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can provide search suggestions for the search UI, which are examples of search queries that may be autocompleted into the field when the user picks them; suggestions give the user an idea of the types of things they can search for&lt;/p&gt;
&lt;p&gt;Search suggestions also render in a platform-appropriate way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;as a menu popover below the field on macOS&lt;/li&gt;
&lt;li&gt;as a complete search results screen on iOS&lt;/li&gt;
&lt;li&gt;as a button that opens the list on watchOS&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Suggestions are provided as an optional view builder closure that returns a set of buttons:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.searchable(text: $text) {
    ForEach(suggestions) { suggestion in
        Button {
            text = suggestion.text
        } label: {
            ColorsSuggestionLabel(suggestion)
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is also a shorthand version available, using the &lt;code&gt;.searchCompletion&lt;/code&gt; modifier&lt;/p&gt;
&lt;p&gt;The modifier should be applied to a non-interactive element like a &lt;code&gt;Label&lt;/code&gt;, and it transforms it into a button which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;updates the search text, like the button above&lt;/li&gt;
&lt;li&gt;dismisses the suggestions view&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.searchable(text: $text) {
    ForEach(suggestions) { suggestion in
        ColorsSuggestionLabel(suggestion)
            .searchCompletion(suggestion.text)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you want to run a search query only when the user submits the search field, not live while they're typing (e.g. because it requires a request to a server), you can use the new &lt;code&gt;.onSubmit&lt;/code&gt; modifier:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.searchable(text: $text) {
    ForEach ...
}
.onSubmit(of: .search) {
    fetchResults()
}&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/direct-and-reflect-focus-swiftui/</id>
    <title>Direct and reflect focus in SwiftUI</title>
    <published>2022-05-17T22:23:54+02:00</published>
    <updated>2022-05-17T22:23:54+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/direct-and-reflect-focus-swiftui/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;SwiftUI in most cases manages focus automatically on your behalf based on given platform's conventions&lt;/p&gt;
&lt;p&gt;In more complex cases, where SwiftUI can't figure this out by itself, there are APIs that let you customize the focus behavior&lt;/p&gt;
&lt;p&gt;Example situations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;focusing the text editor of a new note when the "New" button is pressed in Notes&lt;/li&gt;
&lt;li&gt;moving focus from a vertical list of buttons in the sidebar to a horizontal list of items in the main area on tvOS&lt;/li&gt;
&lt;li&gt;programmatically dismissing the keyboard on iOS&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h3&gt;The @FocusState API&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;A view property marked &lt;code&gt;@FocusState&lt;/code&gt; is a special type of state that changes depending on which of the view's subviews is currently focused:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;enum Field: Hashable {
    case email
    case password
}

struct ContentView: View {
    @FocusState private var focusedField: Field?

    var body: some View {
        VStack {
            TextField("Email", text: $email)
                .focused($focusedField, equals: .email)

            SecureField("Password", text: $password)
                .focused($focusedField, equals: .password)
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In this case, the &lt;code&gt;Field&lt;/code&gt; type is a custom enum type, but you can also use strings, integers or any other &lt;code&gt;Hashable&lt;/code&gt; type&lt;/p&gt;
&lt;p&gt;Notice that the property is an optional, since it's set to &lt;code&gt;nil&lt;/code&gt; if none of the fields is focused&amp;nbsp;– in general, a &lt;code&gt;@FocusState&lt;/code&gt; should always be optional&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The binding is two-way&amp;nbsp;– the value changes when the focus changes, but you can also move the focus by modifying the value&lt;/p&gt;
&lt;p&gt;For example, you can move the focus back to the email field if the user tries to submit the form, but the email is invalid:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;VStack {
    ...
}
.onSubmit {
    if !isEmailValid {
        focusedField = .email
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also dismiss the keyboard when the form is submitted by setting the value to &lt;code&gt;nil&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;VStack {
    ...
}
.onSubmit {
    if !isEmailValid {
        focusedField = .email
    } else {
        focusedField = nil
        logIn()
    }
}&lt;/pre&gt;
&lt;h3&gt;Creating navigation targets (tvOS)&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;On tvOS, all navigation is done by moving focus and then selecting the focused element&lt;/p&gt;
&lt;p&gt;You may sometimes want to be able to move focus between two parts of a screen with different content, and the default focus navigation will not work automatically if the two elements in two sections are not directly adjacent to each other&lt;/p&gt;
&lt;p&gt;You can fix this by extending the effective focusable area of some elements like buttons to a larger area that is not normally focusable by itself&lt;/p&gt;
&lt;p&gt;This is done using the new &lt;code&gt;.focusSection&lt;/code&gt; API:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;HStack {
    VStack {
        TextField("Email", text: $email)
        SecureField("Password", text: $password)
        SignInWithAppleButton(...)
    }
    .onSubmit { ... }
    .focusSection()

    VStack {
        Image(photoName)
        BrowsePhotosButton()
    }
    .focusSection()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;When the &lt;code&gt;.focusSection()&lt;/code&gt; view modifier is applied to a view like the &lt;code&gt;VStack&lt;/code&gt; here, the view becomes capable of accepting focus as long as it contains any focusable subviews (the browse button)&lt;/p&gt;
&lt;p&gt;Now, the user is able to move from the "Browse photos" button to the login sidebar on the left, even though the buttons in the sidebar aren't directly to the left of the button, and to move right from the email/password fields to the browse button, even though the button isn't directly to the right&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/whats-new-in-swiftui/</id>
    <title>What's new in SwiftUI</title>
    <published>2022-05-15T00:48:11+02:00</published>
    <updated>2022-05-15T00:48:11+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/whats-new-in-swiftui/"/>
    <content type="html">&lt;h3&gt;Async images&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Built-in support for loading images asynchronously:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;AsyncImage(url: photo.url)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;AsyncImage&lt;/code&gt; provides a default placeholder, but it can also be customized:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;AsyncImage(url: photo.url) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fill)
} placeholder: {
    Color.blue
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Custom animations and error handling:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;AsyncImage(
    url: photo.url,
    transaction: .init(animation: .spring())
) { phase in
    switch phase {
        case .empty: ...
        case .success(let image): ...
        case .failure(let error): ...
    }
}&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Improvements to lists&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Pull to refresh:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Support for pull-to-refresh in lists on iOS:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;List {
    ...
}
.refreshable {
    await items.reload()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Tasks:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Asynchronous task associated with a view:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.task {
    await items.load()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The task is run when the view appears and is automatically cancelled when the view is removed&lt;/p&gt;
&lt;p&gt;This can be also used to load data from an async sequence that produces items continuously for the whole duration of the view being visible&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;See more about new concurrency features in "&lt;a href="/notes/wwdc21/discover-concurrency-in-swiftui/"&gt;Discover concurrency in SwiftUI&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Bindings to list items:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A &lt;code&gt;List&lt;/code&gt; can now accept a binding to an array property and will pass a binding to each specific item to the closure&lt;/p&gt;
&lt;p&gt;This lets you use controls that require a binding (like &lt;code&gt;TextField&lt;/code&gt;) within list items:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;List($items) { $item in
    Text(item.title)
    TextField("Item", text: $item.text)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The previous solution for cases like this was to iterate over list indexes and bind to e.g. &lt;code&gt;$list[index].text&lt;/code&gt;, but this causes SwiftUI to reload the list after every change anywhere&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This is actually a Swift language change, so it works in all previous versions of SwiftUI too&lt;/p&gt;
&lt;p&gt;Also works in &lt;code&gt;ForEach&lt;/code&gt; and some other places&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Customizing lists:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;.listRowSeparatorTint(Color.blue)&lt;/code&gt;&amp;nbsp;– customizing the color of separators&lt;/p&gt;
&lt;p class="arrow"&gt;→ can be defined on the item to give each item a different separator&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;.listRowSeparator(.hidden)&lt;/code&gt;&amp;nbsp;– hide separators completely&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Same for customizing separators between sections: &lt;code&gt;.listSectionSeparator&lt;/code&gt;, &lt;code&gt;.listSectionSeparatorTint&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Swipe actions:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now define custom swipe actions on list items:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;CharacterProfile(character)
.swipeActions {
    Button {
        character.isPinned.toggle()
    } label: {
        Label("Pin", systemImage: "pin")
    }
    .tint(.yellow)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;By default the action is on the right edge&amp;nbsp;– use &lt;code&gt;.swipeActions(edge: .leading)&lt;/code&gt; to put the action on the left edge&lt;/p&gt;
&lt;p&gt;Add multiple &lt;code&gt;.swipeActions&lt;/code&gt; modifiers to include swipe actions on both sides&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Improvements on the Mac:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;List style with alternating backgrounds, like in standard &lt;code&gt;NSTableView&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;.listStyle(.inset(alternatesRowBackgrounds: true)&lt;/code&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;New control for full-featured multi-column tables:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Table(characters) {
    TableColumn("&amp;lt;&amp;gt;") { CharacterIcon($0) }
      .width(20)
    TableColumn("Villain") { Text($0.isVillain ? "Villain" : "Hero") }
      .width(40)
    TableColumn("Name", value: \.name)
    TableColumn("Powers", value: \.powers)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Like a &lt;code&gt;List&lt;/code&gt;, a &lt;code&gt;Table&lt;/code&gt; presents a list of rows rendered from the given content, except a &lt;code&gt;Table&lt;/code&gt; has a defined list of columns&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Table&lt;/code&gt; supports single- and multi-row selection:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@State private var singleSelection: StoryCharacter.ID?
@State private var multiSelection = Set&amp;lt;StoryCharacter.ID&amp;gt;()

Table(characters, selection: $multiSelection) { ... }&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can sort the table by selected column&amp;nbsp;– to support sorting in a column, provide a key-path to a model value which should be used for sorting:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@State private var sortOrder = [KeyPathComparator(\StoryCharacter.name)]

Table(characters, selection: $selection, sortOrder: $sortOrder) {
    TableColumn("Name", value: \.name)
    ...
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Tables also support various visual styles and allow you to customize the appearance of each column&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Integration with Core Data fetch requests:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Fetch requests now provide a binding to their sort descriptors, which lets you build a sortable multi-column table backed by a Core Data request:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@FetchRequest(sortDescriptors: [SortDescriptor(\.name)])
private var characters: FetchedResults&amp;lt;StoryCharacter&amp;gt;

Table(characters, selection: $selection, sortOrder: $characters.sortDescriptors) {
    ...
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;New &lt;code&gt;@SectionedFetchRequest&lt;/code&gt; which lets you build a list divided into sections from a Core Data request:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@SectionedFetchRequest(
    sectionIdentifier: \.isPinned,
    sortDescriptors: [
        SortDescriptor(\.isPinned, order: .reverse),
        SortDescriptor(\.lastModified)
    ],
    animation: .default
)
private var characterSections: SectionedFetchResults&amp;lt;...&amp;gt;&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The value of the property is a list of sections, each containing a list of items:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;List {
    ForEach(characterSections) { section in
        Section(section.id)
        ForEach(section) { character in
            CharacterRowView(character)
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Support for search in lists:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.searchable(text: $characters.filterText)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;SwiftUI automatically adds a search field in the appropriate place depending on the platform, and may automatically provide suggestions based on the context&lt;/p&gt;
&lt;p&gt;The modifier takes a binding to the entered filter text, which you should use to filter the results&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;See more in "&lt;a href="/notes/wwdc21/craft-search-experiences-in-swiftui/"&gt;Craft search experiences in SwiftUI&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Sharing data with other apps&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Support for customized previews during drag &amp;amp; drop operation:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;CharacterIcon(character)
.onDrag {
    character.itemProvider
} preview: {
    CharacterDragPreview(character)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;New ways to import and export data to/from your app using Item Providers&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Importing items from outside:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.importsItemProviders(StoryCharacter.imageAttachmentTypes) { itemProviders in
    guard let firstItem = itemProviders.first else { return false }

    Task {
        selectedCharacter.imageAttachment = await StoryCharacter.loadImage(from: firstItem)
    }

    return true
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This lets you e.g. import an image taken with an iPhone's camera to a Mac app using the Continuity Camera feature&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;ImportFromDevicesCommands()&lt;/code&gt; helper lets you add the necessary menu entries (File &amp;gt; Import from iPhone or iPad):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;WindowGroup { ... }
    .commands {
        ImportFromDevicesCommands()
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Exporting items to another app, e.g. to Shortcuts:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;.exportsItemProviders(StoryCharacter.contentTypes) {
    if let selectedCharacter = selectedCharacter {
        return [selectedCharacter.itemProvider]
    } else {
        return []
    }
}&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Graphics&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;SF Symbols:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Many new symbols&lt;/p&gt;
&lt;p&gt;Two new rendering modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hierarchical&amp;nbsp;– uses one color like monochrome, but adds multiple levels of opacity to emphasize key elements of the symbol&lt;/li&gt;
&lt;li&gt;palette&amp;nbsp;– gives you fine grained control over which parts of the symbol should be filled with what color&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;New SwiftUI colors: mint, teal, cyan, indigo, brown&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Symbol variants like "filled" are now automatically chosen for you depending on the context&lt;/p&gt;
&lt;p&gt;So if e.g. filled symbols should be used in tab bars, you can just use &lt;code&gt;"person.circle"&lt;/code&gt; instead of &lt;code&gt;"person.circle.fill"&lt;/code&gt; and the framework will choose the right variant automatically&lt;/p&gt;
&lt;p&gt;This makes your code more reusable&amp;nbsp;– e.g. you can use the same tab bar on macOS and it will use the outline versions of the same symbols there&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;More in "&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10097/"&gt;What's new in SF Symbols&lt;/a&gt;" and "&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10349"&gt;SF Symbols in SwiftUI&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Canvas view:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A new view that gives you a rectangular canvas for free drawing, like custom &lt;code&gt;NSViews/UIViews&lt;/code&gt; with &lt;code&gt;drawRect:&lt;/code&gt; in &lt;code&gt;AppKit/UIKit&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can use this to draw a specific custom element, or to draw multiple elements like a grid of small icons that don't need individual tracking and invalidation&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Canvas { context, size in
    for symbol in symbols {
        let image = context.resolve(symbol.image)
        context.draw(image, in: symbol.rect)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To provide accessibility to a &lt;code&gt;Canvas&lt;/code&gt; element, you can use the new &lt;code&gt;.accessibilityChildren&lt;/code&gt; modifier:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Canvas {
    ...
}
.accessibilityChildren {
    List(symbols) { Text($0.name) }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This provides a completely separate view hierarchy to accessibility interfaces which is more readable than the actual view&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Timeline view:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;TimelineView&lt;/code&gt; is a new view that generates different content based on the time of rendering&lt;/p&gt;
&lt;p&gt;Can be used to build e.g. an animated screen saver for tvOS&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;TimelineView(.animation) {
    let time = $0.date.timeIntervalSince1970

    Canvas { context, size in
        // draw items differently based on the time parameter
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The timeline takes a &lt;code&gt;TimelineSchedule&lt;/code&gt; parameter, which specifies when or how often it should be updated&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Timeline view can also be used to provide a view for a Watch app which automatically updates when the app is in dimmed mode on always-on displays&lt;/p&gt;
&lt;p&gt;Also in the dimmed mode on watchOS you can use the new &lt;code&gt;.privacySensitive&lt;/code&gt; modifier to blur out elements which can include private information and should be hidden when the Watch is not being used:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;VStack(alignment: .center) {
    Text("Favorite Symbol")
    Image(systemName: favoriteSymbol)
        .privacySensitive(true)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;.privacySensitive&lt;/code&gt; modifier can also be used in widgets&amp;nbsp;– in this case some content may be hidden (blurred) when the widget is shown on the lock screen while the device is locked&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;More info in "&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10002/"&gt;What's new in watchOS 8&lt;/a&gt;" and "&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10048/"&gt;Principles of great widgets&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Materials:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now create views with material backgrounds (the ones with a translucent blur effect):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;VStack {
    Text("Symbol Browser")
        .font(.largeTitle)
    Text("\(symbols.count) symbols")
        .foregroundStyle(.secondary)
        .font(.title2)
}
.padding()
.background(.ultraThinMaterial,
  in: RoundedRectangle(cornerRadius: 16.0))&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;When you use semantic styles like &lt;code&gt;.foregroundStyle(.secondary)&lt;/code&gt; for the text, the content of a container with material background uses the appropriate "vibrancy" effect&lt;/p&gt;
&lt;p&gt;The effect is applied to text, with the exception of emoji, which are excluded because this would render them an in incorrect way&lt;/p&gt;
&lt;p&gt;On the Mac, system controls like sidebars and popups use the material background and now also apply the vibrancy effect to their content&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;More about canvas and materials in the "&lt;a href="/notes/wwdc21/add-rich-graphics-to-swiftui-app/"&gt;Add rich graphics to your SwiftUI app&lt;/a&gt;" talk&lt;/p&gt;
&lt;p&gt;You can also read more about vibrancy and materials in my old blog post about the introduction of dark mode in macOS Mojave&amp;nbsp;– "&lt;a href="/2018/07/04/dark-side-mac-1/"&gt;Dark Side of the Mac: Appearance &amp;amp; Materials&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Safe area inset:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is a new view modifier &lt;code&gt;.safeAreaInset&lt;/code&gt; that allows you to position a view on top of a &lt;code&gt;ScrollView&lt;/code&gt; in a way that adjusts the scroll view's top/bottom insets so that all the content can be reached and the beginning/end isn't obscured by the overlay:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ScrollView {
    ...
}
.safeAreaInset(edge: .bottom, spacing: 0) {
    VStack {
        Text("\(symbols.count) symbols selected")
    }
    .padding()
    .background(.regularMaterial)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;See more in the "&lt;a href="/notes/wwdc21/add-rich-graphics-to-swiftui-app/"&gt;Add rich graphics to your SwiftUI app&lt;/a&gt;" talk&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Preview enhancements:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;New &lt;code&gt;.previewInterfaceOrientation&lt;/code&gt; modifier for choosing portrait/landscape orientation:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;static var previews: some View {
    ColorList()
        .previewInterfaceOrientation(.vertical)

    ColorList()
        .previewInterfaceOrientation(.horizontal)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The Attributes Inspector in the preview now includes a section for accessibility attributes, and there is a new inspector tab that shows the accessibility properties of all view elements at a glance&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Text and keyboard&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Markdown support:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;Text&lt;/code&gt; now automatically interprets Markdown formatting:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Text("**Hello**, [WWDC](https://developer.apple.com/wwdc21)!")
Text("`print(helloText)`")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This is built on top of the new Swift-native &lt;code&gt;AttributedString&lt;/code&gt; in Foundation&lt;/p&gt;
&lt;p&gt;It has a new rich, type-safe API for adding attributes, and even allows defining custom attributes that can be applied to text through the Markdown syntax&lt;/p&gt;
&lt;p&gt;See more in "&lt;a href="/notes/wwdc21/whats-new-in-foundation/"&gt;What's new in Foundation&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Localization:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Xcode 13 can now automatically extract strings for localization using the Swift compiler (see option in build settings)&lt;/p&gt;
&lt;p&gt;See more in "&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10220/"&gt;Localize your SwiftUI app&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Type:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now restrict some content to only a specified range of Dynamic Type text sizes&amp;nbsp;– so that when the user picks one of the extra large sizes which would make your text too large to fit, it stays at the maximum allowed size:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Header("Today's Activities")
    .dynamicTypeSize(.large .. .extraExtraLarge)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Text selection:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can enable selection of non-editable text:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Text(activity.description)
    .textSelection(.enabled)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This allows the user to select and copy any part of the text on macOS&lt;/p&gt;
&lt;p&gt;On iOS this only allows copying the whole text though&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;New formatter APIs:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Foundation now includes new &lt;code&gt;.formatted()&lt;/code&gt; APIs for various values like dates that allow you to specify the format in a type-safe way:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Text(date.formatted())

Text(date.formatted(date: .omitted, time: .shortened))

Text(date.formatted(
    .dateTime.weekday(.wide).day().month().hour().minute()))&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;There's even a list formatter API like &lt;code&gt;ListFormatter&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;people.map(\.nameComponents).formatted(
    .list(memberStyle: .name(style: .short), type: .and))

// =&amp;gt; "Matt, Jacob, and Taylor"&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Format styles can also be specified in &lt;code&gt;TextField&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@State private var newAttendee = PersonNameComponents()

var body: some View {
    ...
    TextField("New Person", value: $newAttendee, format: .name(style: .medium))
    ...
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The entered text is automatically validated and reformatted according to the specified format, and then parsed into a value of a correct type if possible&lt;/p&gt;
&lt;p&gt;See more about the new format styles in "&lt;a href="/notes/wwdc21/whats-new-in-foundation/"&gt;What's new in Foundation&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Text field prompts:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;TextField&lt;/code&gt; now includes a &lt;code&gt;prompt:&lt;/code&gt; parameter, which allows you to specify a placeholder value (an example text to enter) that is different from the label (which normally should specify the name or meaning of the field)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Text fields inside a &lt;code&gt;Form&lt;/code&gt; on macOS now display with labels outside on the left, right-aligned to the text field, with the prompt (if any) shown as the placeholder&lt;/p&gt;
&lt;p class="arrow"&gt;→ previously they were rendered like on iOS, with the label used as the placeholder inside the field&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Text field submission:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;New &lt;code&gt;.onSubmit&lt;/code&gt; modifier allows you to specify an action to execute when a text field is submitted by pressing a physical or virtual Return key:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;TextField(
    "New Person",
    value: $newAttendee,
    format: .name(style: .medium)
)
.onSubmit {
    people.append(Person(newAttendee))
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This can also be applied to the entire form:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Form {
    TextField("Username", text: $viewModel.userName)
    SecureField("Password", text: $viewModel.password)
}
.onSubmit(of: .text) {
    guard viewModel.validate() else { return }
    viewModel.login()
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now also configure the title of the Return key on the iOS keyboard (which gives it a blue background):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;TextField(...)
.onSubmit {
    ...
}
.submitLabel(.done)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Keyboard accessory views:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Adding accessory views to the iOS keyboard&amp;nbsp;– configure toolbar items with the placement of &lt;code&gt;.keyboard&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Form {
    ...
}
.toolbar {
    ToolbarItemGroup(placement: .keyboard) {
        Button(action: selectPreviousField) {
            Label("Previous", systemImage: "chevron.up")
        }
        .disabled(!hasPreviousField)

        Button(action: selectNextField) {
            Label("Next", systemImage: "chevron.down")
        }
        .disabled(!hasNextField)
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p class="arrow"&gt;→ on macOS the buttons will be shown in the Touch Bar instead&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Focus state:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;New property wrapper &lt;code&gt;@FocusState&lt;/code&gt; that reflects the state of focus and provides precise control over it&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;@FocusState&lt;/code&gt; property is bound to a field's focus using &lt;code&gt;.focused&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The value of the property will then show if and which view is currently focused, and you can move the focus by modifying the value&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Simple form: declare a boolean property, and it will be set to true when the view is focused:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@FocusState private var newPersonIsFocused: Bool

var body: some View {
    TextField("New Person", value: $newAttendee,
        format: .name(style: .medium)
    )
    .focused($newPersonIsFocused)

    Button {
        newPersonIsFocused = true
    } label: {
        Label("Add Attendee", systemImage: "plus")
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In a more advanced version, the focus property can use any &lt;code&gt;Hashable&lt;/code&gt; as its type, e.g. an enum, and be used to show &lt;em&gt;which&lt;/em&gt; of the possible fields is currently focused:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;private enum Field: Int, Hashable {
    case name, location, date, addAttendee
}

@FocusState private var focusedField: Field?

var body: some View {
    TextField("New Person", value: $newAttendee,
        format: .name(style: .medium)
    )
    .focused(focusedField, equals: .addAttendee)

    Button {
        focusedField = .addAttendee
    } label: {
        Label("Add Attendee", systemImage: "plus")
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This allows you to build any kind of complex screen with multiple focusable fields that you may want to move between programmatically&lt;/p&gt;
&lt;p&gt;You can also use &lt;code&gt;.focused&lt;/code&gt; to dismiss the keyboard on iOS by unfocusing a focused field:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@FocusState private var newPersonIsFocused: Bool

var body: some View {
    TextField(...).focused($newPersonIsFocused)
}

func endEditing() {
    newPersonIsFocused = false
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;See more about the new focus API in "&lt;a href="/notes/wwdc21/direct-and-reflect-focus-swiftui/"&gt;Direct and reflect focus in SwiftUI&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Buttons&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Support for bordered buttons on iOS:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Button("Add") { ... }
    .buttonStyle(.bordered)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Like all modifiers, this can be added to a larger container and applies to all buttons inside&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Choose a color for a bordered button by specifying a tint:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Button("Buy") { ... }
    .buttonStyle(.bordered)
    .tint(.green)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Make buttons smaller or larger by using the &lt;code&gt;.controlSize&lt;/code&gt; modifier, previously used only on macOS:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ForEach(entry.tag) { tag in
    Button(tag.name) { ... }
        .tint(tag.color)
}
.controlSize(.small)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p class="arrow"&gt;→ this only seems to work for buttons at the moment&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also make "prominent" bordered buttons which use a style that stands out more (stronger background):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Button("Submit", action: submitForm)
    .buttonStyle(.borderedProminent)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p class="help"&gt;ℹ️ In the video, this is shown as &lt;code&gt;.controlProminence(.increased)&lt;/code&gt;. The &lt;code&gt;.controlProminence&lt;/code&gt; modifier was replaced in a later beta with a separate &lt;code&gt;.borderedProminent&lt;/code&gt; button style.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Buttons with a &lt;code&gt;.large&lt;/code&gt; size can be used to build a menu of action buttons at the bottom of a screen/form, with the default button marked as prominent:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;VStack {
    Button(action: addtoJar) {
        Text("Add to Jar").frame(maxWidth: 300)
    }
    .buttonStyle(.borderedProminent)
    .keyboardShortcut(.defaultAction)

    Button(action: addToWatchlist) {
        Text("Add to Watchlist").frame(maxWidth: 300)
    }
    .tint(.accentColor)
    .buttonStyle(.bordered)
}
.controlSize(.large)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Note: do not add &lt;code&gt;.borderedProminent&lt;/code&gt; to every single button on the screen&amp;nbsp;– this should be only used for single primary actions&lt;/p&gt;
&lt;p class="arrow"&gt;→ this is the equivalent of a blue-colored default button in a dialog on macOS, one that is bound to the Return key shortcut, of which by definition there can be only one within a dialog&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Buttons using the new styles like &lt;code&gt;.controlSize&lt;/code&gt;, &lt;code&gt;.borderedProminent&lt;/code&gt; and &lt;code&gt;.tint&lt;/code&gt; automatically provide appropriate pressed states and automatically adapt to dark mode, Dynamic Type font sizes etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now specify a "role" for a button, currently &lt;code&gt;.cancel&lt;/code&gt; or &lt;code&gt;.destructive&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Button("Delete...", role: .destructive) { ... }&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Marking a button as destructive adds a red foreground and/or background depending on the context, to emphasize that the action is potentially dangerous&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Cancel and destructive buttons are often used in confirmation dialogs, which now have a new view modifier made specifically for them:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;RowCell(entry)
.contextMenu {
    Button("Delete...", role: .destructive) {
        showConfirmation = true
    }
}
.confirmationDialog("Are you sure you want to delete \(title)?",
    isPresented: $showConfirmation
) {
    Button("Delete", role: .destructive) {
        deleteEntry(entry)
    }
} message:
    Text("Deleting \(title) will remove it from all of your jars.")
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;A confirmation dialog is shown as an action sheet on iOS, as a popover on the iPad, and as an alert on macOS&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Another type of button is a menu button (available before):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Menu("Add") {
    ForEach(jarStore.allJars) { jar in
        Button("Add to \(jar.name)") {
            jarStore.add(buttonEntry, to: jar)
        }
    }
}
.menuStyle(.borderedButton)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p class="help"&gt;ℹ️ In the video, this is shown as &lt;code&gt;.menuStyle(.button)&lt;/code&gt;. There is however no such menu style as &lt;code&gt;.button&lt;/code&gt;&amp;nbsp;– use &lt;code&gt;.borderedButton&lt;/code&gt; on macOS and &lt;code&gt;.borderlessButton&lt;/code&gt; elsewhere (or just keep the default).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A bordered button menu is rendered as a button with a down arrow on the right on macOS (a &lt;a href="https://developer.apple.com/design/human-interface-guidelines/macos/buttons/pull-down-buttons/"&gt;pull-down menu button&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A new option for button menus (both macOS and iOS) is to provide a "primary action":&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Menu("Add") {
    ForEach(jarStore.allJars) { jar in
        Button("Add to \(jar.name)") {
            jarStore.add(buttonEntry, to: jar)
        }
    }
} primaryAction: {
    jarStore.add(buttonEntry)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This makes the button act as both a normal button and a menu:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on iOS, a normal tap runs the primary action, and a long press shows the menu&lt;/li&gt;
&lt;li&gt;on macOS, the button uses a slightly different style and lets you either click the main button part for the primary action, or click the arrow on the right to show the menu (without a primary action, clicking either of the parts shows the menu)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also choose to hide the arrow indicator of a menu button on macOS using &lt;code&gt;.menuIndicator&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Menu("Add") {
    ...
}
.menuStyle(.borderedButton)
.menuIndicator(.hidden)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Such menu button looks like a completely plain button, but still acts as a menu button:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;without a primary action, it always shows the menu when pressed&lt;/li&gt;
&lt;li&gt;with a primary action, it runs the primary action when pressed, and to show the menu you need to do a long-press like on iOS&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can use such buttons with a hidden indicator to decrease the visual prominence of the button, e.g. if there are a lot of them in the same view&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A &lt;code&gt;Toggle&lt;/code&gt; can now also be displayed as a toggle button:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;Toggle(isOn: $showOnlyNewFilter) {
    Label("Show Only New", systemImage: "sparkles")
}
.toggleStyle(.button)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;This works like a toggle button on macOS (&lt;code&gt;NSButton.ButtonType.toggle&lt;/code&gt;)&amp;nbsp;– one that cycles between an "on" state (with a blue background) and an "off" state when pressed; it works the same way on iOS&amp;nbsp;– in the "on" state it's rendered as a bordered button with a blue background&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is also a new control that groups buttons, mainly toolbar buttons&amp;nbsp;– &lt;code&gt;ControlGroup&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ControlGroup {
    Button(action: archive) {
        Label("Archive", systemImage: "archiveBox")
    }
    Button(action: delete) {
        Label("Delete", systemImage: "trash")
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Toolbar buttons in a &lt;code&gt;ControlGroup&lt;/code&gt; are displayed grouped together visually, as a kind of segmented control&amp;nbsp;– as opposed to buttons in &lt;code&gt;ToolbarItemGroup&lt;/code&gt;, which are only arranged side by side, but not joined visually in any way&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;ControlGroup&lt;/code&gt; with menu buttons can be used to create a pair of standard back/forward navigation buttons:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;ControlGroup {
    Menu {
        ForEach(history) { ... }
    } label: {
        Label("Back", systemImage: "chevron.backward")
    } primaryAction: {
        goBack(to: history[0])
    }
    .disabled(history.isEmpty)

    Menu {
        ForEach(forwardHistory) { ... }
    } label: {
        Label("Forward", systemImage: "chevron.forward")
    } primaryAction: {
        goForward(to: forwardHistory[0])
    }
    .disabled(forwardHistory.isEmpty)
}&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc20/app-essentials-in-swiftui/</id>
    <title>App essentials in SwiftUI</title>
    <published>2022-05-09T22:47:08+02:00</published>
    <updated>2022-05-09T22:47:08+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc20/app-essentials-in-swiftui/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;You can now build the entire app using just SwiftUI&lt;/p&gt;
&lt;p&gt;New APIs for defining apps and their scenes and views&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Defining apps &amp; scenes&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;An app consists of one or more &lt;em&gt;"scenes"&lt;/em&gt;&lt;/p&gt;
&lt;p class="arrow"&gt;→ see "&lt;a href="/notes/wwdc19/introducing-multiple-windows-on-ipad/"&gt;Introducing Multiple Windows on iPad&lt;/a&gt;" from 2019 for an introduction to the UIKit scenes API&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A scene roughly maps to a window:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on iOS, watchOS and tvOS the app can present one scene at a time&lt;/li&gt;
&lt;li&gt;on iPadOS you can see multiple scenes from the same or different apps side by side&lt;/li&gt;
&lt;li&gt;on macOS, each scene is displayed in a different window or inside tabs within a window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each scene contains a view hierarchy and/or a hierarchy of child scenes&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;An app declaration looks similar to a view declaration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it's also a lightweight struct&lt;/li&gt;
&lt;li&gt;it implements the &lt;code&gt;App&lt;/code&gt; protocol similar to &lt;code&gt;View&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the struct can include properties defining data dependencies, marked with wrappers like &lt;code&gt;@State&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the protocol requires a single &lt;code&gt;body&lt;/code&gt; property which returns &lt;code&gt;some Scene&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;inside the body definition, a DSL similar to the one for views is used to define the scene hierarchy:&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@main
struct BookClubApp: App {
  @StateObject private var store = ReadingListStore()

  var body: some Scene {
    WindowGroup {
      ReadingListView(store: store)
    }
  }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;@main&lt;/code&gt; attribute is a new feature of Swift 5.3&amp;nbsp;– it allows a type to serve as the program entry point&lt;/p&gt;
&lt;p&gt;This replaces the &lt;code&gt;@NSApplicationMain&lt;/code&gt;/&lt;code&gt;@UIApplicationMain&lt;/code&gt; or &lt;code&gt;main.swift&lt;/code&gt; file&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The code above is a complete declaration of the app structure&lt;/p&gt;
&lt;p&gt;Even though it's just a few lines of code, this automatically provides quite a lot of functionality for free&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Window Group&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The most common type of scene is a &lt;code&gt;WindowGroup&lt;/code&gt;, which defines the primary interface (main view) of your app&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A &lt;code&gt;WindowGroup&lt;/code&gt; displays its view in the expected way or each platform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on iPhone, tvOS and watchOS it's shown as a single full-screen window&lt;/li&gt;
&lt;li&gt;on the iPad it supports the new multi-window scene system&lt;/li&gt;
&lt;li&gt;on the Mac it creates multiple Mac windows&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The lifecycle of scenes depends on the platform&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;On iPadOS, the app switcher shows the app name and an optional scene title above each scene/window&amp;nbsp;– this can be the document title, title of the open web page etc.&lt;/p&gt;
&lt;p&gt;In latest SwiftUI, you can set that scene title using the new &lt;code&gt;.navigationTitle("Name")&lt;/code&gt; view modifier, which replaces &lt;code&gt;.navigationBarTitle&lt;/code&gt; (it sets both the title seen in the navigation bar and the scene name in the app switcher)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;On the Mac, &lt;code&gt;WindowGroup&lt;/code&gt; automatically provides a "File &amp;gt; New Window" command that opens a new window and a Window menu that lists all windows&lt;/p&gt;
&lt;p&gt;The scene title is used as the window title in the title bar and the Window menu&lt;/p&gt;
&lt;p&gt;Scene windows can also me merged into a single window with tabs by choosing Window &amp;gt; Merge All Windows&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Scene storage&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;@SceneStorage&lt;/code&gt; is a new property wrapper that helps you manage persistence and restoration of view state in scenes&lt;/p&gt;
&lt;p&gt;Provide a unique key under which the given value should be saved, and it will be saved and restored at appropriate times automatically&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;struct ReadingListViewer: View {
  @SceneStorage("selectedItem") var selectedItem: String?

  var body: some View {
    NavigationView {
      ReadingList(selectedItem: $selectedItem)
    }
  }
}&lt;/pre&gt;
&lt;h3&gt;Document Group&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Another basic kind of scene in the app definition is &lt;code&gt;DocumentGroup&lt;/code&gt;, which defines documents in a document-based app:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@main
struct ShapeEditApp: App {
  var body: some Scene {
    DocumentGroup(newDocument: SketchDocument()) { file in
      DocumentView(file.$document)
    }
  }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;See more in "&lt;a href="/notes/wwdc20/build-document-based-apps-in-swiftui/"&gt;Build document-based apps in SwiftUI&lt;/a&gt;"&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Preferences windows&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The last scene type, &lt;code&gt;Settings&lt;/code&gt;, is Mac-only and defines standard Mac Preferences windows:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var body: some Scene {
  WindowGroup {
    ...
  }

  Settings {
    BooksSettingsView()
  }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The Preferences window is automatically linked to from the main app menu with the standard &lt;code&gt;Cmd+,&lt;/code&gt; keyboard shortcut&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Menu commands&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The new &lt;code&gt;.commands&lt;/code&gt; view modifier allows you to define menus and menu items with shortcuts for the macOS app menu:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;WindowGroup {
  ...
}
.commands {
  BookCommands()
}&lt;/pre&gt;
&lt;pre class="brush: swift"&gt;struct BookCommands: Commands {
  @FocusedBinding(\.selectedBook) private var selectedBook: Book?

  var body: some Commands {
    CommandMenu("Book") {
      Section {
        Button("Update Progress...", action: updateProgress)
          .keyboardShortcut("u")
        Button("Mark Completed", action: markCompleted)
          .keyboardShortcut("C")
      }
      .disabled(selectedBook == nil)
    }
  }

  private func updateProgress() { selectedBook?.recordNewProgress() }
  private func markCompleted() { selectedBook?.markCompleted() }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The commands are also used on the iPad and are displayed in the keyboard shortcuts help popup&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;@FocusedBinding&lt;/code&gt; property wrapper used in this commands block allows you to selectively enable/disable some groups of commands based on your focus, similar to how the responder chain is used in AppKit or UIKit&lt;/p&gt;
&lt;p&gt;See more about commands in the reference documentation&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc21/whats-new-in-appkit/</id>
    <title>What's new in AppKit</title>
    <published>2021-06-11T12:41:10+02:00</published>
    <updated>2021-06-11T12:41:10+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc21/whats-new-in-appkit/"/>
    <content type="html">&lt;h3&gt;Design &amp; control updates&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are design updates for some system controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;popovers appear with an animation&lt;/li&gt;
&lt;li&gt;sliders smoothly glide into position when clicked&lt;/li&gt;
&lt;li&gt;smaller things like increased spacing between table sections or slightly wider toolbar buttons&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Control tinting:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Individual controls like buttons, segmented controls or sliders can have a custom tint&lt;/p&gt;
&lt;p&gt;Properties: &lt;code&gt;NSButton.bezelColor&lt;/code&gt;, &lt;code&gt;NSSegmentedControl.selectedSegmentBezelColor&lt;/code&gt;, &lt;code&gt;NSSlider.trackFillColor&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;These properties have been introduced in macOS Sierra for Touch Bar controls; in macOS Monterey they’re functional also for normal app controls&lt;/p&gt;
&lt;p&gt;This is useful for specific controls that need to have some kind of semantically meaningful color&lt;/p&gt;
&lt;p class="arrow"&gt;→ e.g. a green “Accept Call” and a red “End Call” buttons in a video call app&lt;/p&gt;
&lt;p&gt;Avoid confusion with the default button if there is one in the same view, since it will also be colorful&lt;/p&gt;
&lt;p&gt;Make sure to indicate purpose with more than just the color (using a clear label or an icon), since some of your users may not be able to distinguish buttons by color&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Push buttons:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Push buttons no longer highlight using the accent color on click&amp;nbsp;– they behave just like e.g. segmented controls in macOS Big Sur&lt;/p&gt;
&lt;p&gt;Don’t make assumptions about how the highlight state looks (e.g. drawing white text over a button that should be blue when pressed, but will now be light gray)&lt;/p&gt;
&lt;p&gt;Instead, check the interior background style &lt;code&gt;NSButtonCell.interiorBackgroundStyle&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.normal&lt;/code&gt; = colorless state&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.emphasized&lt;/code&gt; = colorful state&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The old “regular square button” aka “bevel button” has now been refreshed as “Flexible push style button” and can be used as a variable height push button&lt;/p&gt;
&lt;p&gt;It supports the same kind of configuration as a regular push button, so it can serve as a default button and can be tinted&lt;/p&gt;
&lt;p&gt;Its corner radius and padding now match other button styles&lt;/p&gt;
&lt;p&gt;It can contain larger icons or multi-line text&lt;/p&gt;
&lt;p&gt;The vast majority of buttons should still use the standard fixed height push button&amp;nbsp;– the variable height button is meant for special cases&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Localizing keyboard shortcuts&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Some keyboard shortcuts should be localized for different keyboard layouts, because in some layouts they may be hard or impossible to type, or it may make sense to adapt them for right-to-left languages&lt;/p&gt;
&lt;p&gt;E.g. &lt;code&gt;Cmd + \&lt;/code&gt; is not possible to type on the Japanese keyboard, which doesn’t have a backslash key&lt;/p&gt;
&lt;p&gt;AppKit can now handle this for you&lt;/p&gt;
&lt;p&gt;In macOS Monterey, the system automatically remaps such shortcuts to different ones that are more natural on the given keyboard layout&lt;/p&gt;
&lt;p&gt;Shortcuts like &lt;code&gt;Cmd + [&lt;/code&gt; and &lt;code&gt;Cmd + ]&lt;/code&gt; to go back and forward will be swapped in right-to-left languages&lt;/p&gt;
&lt;p class="arrow"&gt;→ this applies to brackets, braces, parentheses and arrow keys&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can opt out using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NSMenuItem.allowsAutomaticKeyEquivalentMirroring&lt;/code&gt;&amp;nbsp;– for directional keys like brackets&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSMenuItem.allowsAutomaticKeyEquivalentLocalization&lt;/code&gt;&amp;nbsp;– turns off all key localization, including mirroring&lt;/li&gt;
&lt;li&gt;if you really don’t want to use this feature at all, you can also disable it completely in your app by implementing the &lt;code&gt;NSApplicationDelegate&lt;/code&gt; method: &lt;code&gt;applicationShouldAutomaticallyLocalizeKeyEquivalents(_:)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h3&gt;Update to SF Symbols&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;New version&amp;nbsp;– SF Symbols 3&lt;/p&gt;
&lt;p&gt;Expands capabilities of the SF Symbols app&lt;/p&gt;
&lt;p&gt;Some symbols now have multiple layers that can be individually colored&lt;/p&gt;
&lt;p&gt;Updated format for custom symbols&amp;nbsp;– allows you to annotate distinct layers within an image&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Big Sur had two coloring modes for symbols:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;traditional monochrome template style, drawing the whole symbol using one accent color&lt;/li&gt;
&lt;li&gt;a multicolor style that uses multiple colors that are predefined in the symbol itself&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;SF Symbols 3 in macOS Monterey adds two new rendering modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;"hierarchical"&amp;nbsp;– uses a single tint color, but draws different layers of the image in an emphasized or deemphasized way (lighter or darker than the base color)&lt;/li&gt;
&lt;li&gt;"palette"&amp;nbsp;– lets you assign each layer any custom color independently&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;APIs for the new rendering modes:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;NSImage.SymbolConfiguration(hierarchicalColor: .red)
NSImage.SymbolConfiguration(paletteColors: […])
NSImage.SymbolConfiguration.preferringMulticolor()&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Symbol variants:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are also new APIs for mapping between symbol variants, e.g. outline heart symbol &amp;nbsp;⭤&amp;nbsp; filled heart symbol, or variants with circles etc.&lt;/p&gt;
&lt;p&gt;Useful e.g. when you’re building a picker control that uses outline icons for unselected states and filled variants of the same icons for the selected item&lt;/p&gt;
&lt;p&gt;To convert between variants, call e.g.: &lt;code&gt;baseImage.image(with: .fill)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;There are constants for each kind of symbol variant, and you can combine multiple variants together (e.g. circle + fill)&lt;/p&gt;
&lt;p&gt;See “Design and build SF Symbols” for more info&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;TextKit 2&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Huge update to the text system&lt;/p&gt;
&lt;p&gt;TextKit is a great text engine with a long track record, used across all Apple systems&lt;/p&gt;
&lt;p&gt;However, TextKit is a &lt;em&gt;linear&lt;/em&gt; text layout engine, which means it typesets a block of text from the beginning to the end&lt;/p&gt;
&lt;p&gt;There are a lot of use cases where a &lt;em&gt;non-linear&lt;/em&gt; layout engine is more useful&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;TextKit 2 always uses a non-linear layout system&lt;/p&gt;
&lt;p&gt;This means it can perform layout on a more granular level, which allows it to avoid some unnecessary work&lt;/p&gt;
&lt;p&gt;For example, when you’re looking at a middle fragment of a long document, a linear layout system needs to process all text from the beginning up to the given fragment in order to render it; a non-linear system can start at the nearest start of a paragraph&lt;/p&gt;
&lt;p&gt;The non-linear layout system also makes it easier to mix text with non-text elements, and improves performance for large documents&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;TextKit 2 provides a lot of customization points, which allow you to extend its behavior&lt;/p&gt;
&lt;p&gt;The new version coexists with TextKit 1, you can choose which engine to use for each view&lt;/p&gt;
&lt;p&gt;TextKit 2 has actually already been used in some system apps and controls in Big Sur&lt;/p&gt;
&lt;p&gt;See “&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10061/"&gt;Meet TextKit 2&lt;/a&gt;” for more info&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;New Swift features&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Swift 5.5 introduces some important new features for managing concurrency: async/await and actors&lt;/p&gt;
&lt;p&gt;In AppKit, many asynchronous methods that return value through a completion handler now have variants that work with async/await:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@IBAction func pickColor(_ sender: Any?) {
  async {
    guard let color = await NSColorSampler().sample() else { return }
    textField.textColor = color
  }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The actor model is a great fit for a UI framework like AppKit where most APIs should be called on a single main thread&lt;/p&gt;
&lt;p&gt;The macOS SDK now has a &lt;code&gt;@MainActor&lt;/code&gt; property wrapper that marks all types that have to be accessed from the main thread&lt;/p&gt;
&lt;p&gt;Classes such as &lt;code&gt;NSView&lt;/code&gt;, &lt;code&gt;NSView/WindowController&lt;/code&gt;, &lt;code&gt;NSApplication&lt;/code&gt;, &lt;code&gt;NSCell&lt;/code&gt;, &lt;code&gt;NSDocument&lt;/code&gt; etc. are now marked with &lt;code&gt;@MainActor&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Code running in the main actor can freely call methods on other main actor types&lt;/p&gt;
&lt;p&gt;However, code that isn’t running on the main thread needs to use async/await to run code on a &lt;code&gt;@MainActor&lt;/code&gt; type&lt;/p&gt;
&lt;p&gt;This is enforced at the compiler level, which lets you avoid common errors that happen when mixing concurrency with UI code&lt;/p&gt;
&lt;p&gt;See “&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10132/"&gt;Meet async/await in Swift&lt;/a&gt;” and “&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10133"&gt;Protect mutable state with Swift actors&lt;/a&gt;” for more info&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;AttributedString:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Swift 5.5 also adds a new value type &lt;code&gt;AttributedString&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It has type-safe attributes and a more swifty API for reading &amp;amp; writing attributes&lt;/p&gt;
&lt;p&gt;You can easily convert between &lt;code&gt;AttributedString&lt;/code&gt; and &lt;code&gt;NSAttributedString&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;See “&lt;a href="/notes/wwdc21/whats-new-in-foundation/"&gt;What’s new in Foundation&lt;/a&gt;” for more info&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Updating NSViews:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is a new Swift property wrapper which should reduce boilerplate around view properties&lt;/p&gt;
&lt;p&gt;Let’s say we have a custom view class like this:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;class BadgeView: NSView {
  var fillColor: NSColor
  var shadow: NSShadow
  var scaling: NSImageScaling
  …
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;These properties will usually need to have a &lt;code&gt;didSet&lt;/code&gt; which updates properties like &lt;code&gt;needsDisplay&lt;/code&gt; or &lt;code&gt;needsLayout&lt;/code&gt; when they’re modified:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;var fillColor: NSColor {
  didSet { needsDisplay = true }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The new &lt;code&gt;@Invalidating&lt;/code&gt; attribute in &lt;code&gt;NSView&lt;/code&gt; lets you easily specify which other view properties should be updated when the given property is modified:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;@Invalidating(.display) var fillColor: NSColor&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Properties that can be invalidated include: display, layout, constraints, intrinsic content size, restorable state&lt;/p&gt;
&lt;p&gt;The marked property needs to be &lt;code&gt;Equatable&lt;/code&gt;, since AppKit checks if the value was actually changed before triggering a view update&lt;/p&gt;
&lt;p&gt;You can extend the invalidation system by conforming to &lt;code&gt;NSViewInvalidating&lt;/code&gt; protocol&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Shortcuts&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;iOS Shortcuts are now available on the Mac&lt;/p&gt;
&lt;p&gt;Shortcuts appear in all the places where you can access services today&amp;nbsp;– if your app supports services, it will also support Shortcuts&lt;/p&gt;
&lt;p&gt;AppKit decides which shortcuts are available at the given place by checking the responder chain&lt;/p&gt;
&lt;p&gt;It asks each responder whether it can provide or receive the type of data used by each shortcut&lt;/p&gt;
&lt;p&gt;The types of data are represented by &lt;code&gt;NSPasteboard.PasteboardType&lt;/code&gt; (usually a UTI)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To support shortcuts in a given responder object, implement the method:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func validRequestor(forSendType sendType: NSPasteboard.PasteboardType?,
                              returnType: NSPasteboard.PasteboardType?) -&amp;gt; Any&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;In that method return an instance of a type implementing &lt;code&gt;NSServicesMenuRequestor&lt;/code&gt; (usually the same object):&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;protocol NSServicesMenuRequestor {
  func writeSelection(to pasteboard: NSPasteboard,
                              types: [NSPasteboard.PasteboardType]) -&amp;gt; Bool

  func readSelection(from pasteboard: NSPasteboard) -&amp;gt; Bool
}&lt;/pre&gt;
&lt;h3&gt;Siri Intents&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now use Siri Intents in a Mac app by adding an Intents Extension&lt;/p&gt;
&lt;p&gt;You can also return an intents handler from the application delegate:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;protocol NSApplicationDelegate {
  optional func application(_ application: NSApplication,
                        handlerFor intent: INIntent) -&amp;gt; Any?
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;The returned object should conform to an appropriate intent handler protocol, depending on the intent type&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc16/cloudkit-best-practices/</id>
    <title>CloudKit Best Practices</title>
    <published>2020-11-14T15:47:57+01:00</published>
    <updated>2020-11-14T15:47:57+01:00</updated>
    <link href="https://mackuba.eu/notes/wwdc16/cloudkit-best-practices/"/>
    <content type="html">&lt;h3&gt;Short CloudKit overview&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Apple uses CloudKit in their applications, so you can be confident that it scales, because for Apple it scales to hundreds of millions of users&lt;/p&gt;
&lt;p&gt;CloudKit lets you focus on building your applications and not worry about building backend services for them&lt;/p&gt;
&lt;p&gt;It provides your users automatic authentication&amp;nbsp;– if the user is logged in to iCloud on their device, they don’t need to log in separately in your app&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A CloudKit container now includes 3 databases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public database for data visible to everyone&lt;/li&gt;
&lt;li&gt;private database for a given user’s private data&lt;/li&gt;
&lt;li&gt;new this year: shared database for user data that they decided to share with others&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Zones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public database has 1 default zone&lt;/li&gt;
&lt;li&gt;private database has a default zone and it can have one or more custom zones&lt;/li&gt;
&lt;li&gt;shared database includes some number of shared zones&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A record always exists in a specific zone&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Building an app with a sync feature&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;A common use case (e.g. Notes app):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user creates some data/records/documents on one of their devices&lt;/li&gt;
&lt;li&gt;later, they open another device and they expect to see these documents there and be able to read/edit them&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The way this is implemented is that CloudKit needs to be the source of truth, and the devices should maintain a local cache of all the app data and synchronize it using CloudKit&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The recommended workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;1. On app launch, fetch changes from the server&lt;/li&gt;
&lt;li&gt;2. Subscribe to any future changes&lt;/li&gt;
&lt;li&gt;3. Fetch changes when you receive a push&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Subscriptions:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Subscriptions let you ask the server to notify you whenever a change happens in the specified set of data. Previously you could subscribe to a specific query to a record type or to all changes in a zone.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;New in iOS 10&amp;nbsp;– &lt;code&gt;CKDatabaseSubscription&lt;/code&gt;&amp;nbsp;– lets you subscribe to all changes in the whole database (private or shared).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Types of subscription notifications:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;ol&gt;
&lt;li&gt;1. Silent push:&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let notificationInfo = CKNotificationInfo()

// we only set this, but none of the UI related keys
notificationInfo.shouldSendContentAvailable = true

// do this once. no need to ask the user for push notifications permission,
// since we won't show any visible notifications
application.registerForRemoteNotifications(…)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;ol&gt;
&lt;li&gt;2. Visual notification:&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let notificationInfo = CKNotificationInfo()

// set any of these
notificationInfo.shouldBadge = true
notificationInfo.alertBody = "alertBody"
notificationInfo.soundName = "default"

// we need to prompt the user for push notification access:
application.registerUserNotificationSettings(…)
application.registerForRemoteNotifications(…)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Remember that push notifications can be coalesced, so you may only get one out of a series. Push notifications tell you that *something* has changed, but not necessarily every single thing that has changed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Creating a subscription:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;This only needs to be done the first time you launch an app&amp;nbsp;– so we set a flag when we create a subscription and the next time we skip this part.&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;if subscriptionIsLocallyCached { return }

let subscription = CKDatabaseSubscription(subscriptionID: "shared-changes")

let notificationInfo = CKNotificationInfo()
notificationInfo.shouldSendContentAvailable = true
subscription.notificationInfo = notificationInfo

let operation = CKModifySubscriptionsOperation(
    subscriptionsToSave: [subscription],
    subscriptionIDsToDelete: []
)

operation.modifySubscriptionsCompletionBlock = { …
    if error != nil {
        …
    } else {
        self.subscriptionIsLocallyCached = true
    }
}

operation.qualityOfService = .utility
self.sharedDB.add(operation)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Listening for pushes:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;ul&gt;
&lt;li&gt;turn on “Remote notifications” and “Background fetch” capabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func application(_ application: UIApplication,
    didReceiveRemoteNotification userInfo: [NSObject: AnyObject],
    fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -&amp;gt; Void) {

    let dict = userInfo as! [String: NSObject]
    let notification = CKNotification(fromRemoteNotificationDictionary: dict)

    if notification.subscriptionID == "shared-changes" {
        fetchSharedChanges {
              completionHandler(.newData)
        }
    }
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Fetching the changes:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ask in which zones something was changed (in shared db&amp;nbsp;– because there may be new zones added when a new user shares some content)&lt;/li&gt;
&lt;li&gt;ask which records have changed in each relevant zone&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The server will not send you pushes about the changes you’re doing on this device, but you may receive those changes you’ve done on the list when fetching a delta download&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;fetchAllChanges&lt;/code&gt;: previously, in some operations you had to manually check for a flag that says there are more results waiting for you that you need to manually request (i.e. another page)&lt;/p&gt;
&lt;p&gt;Now, CloudKit does the paging automatically for you if &lt;code&gt;fetchAllChanges = true&lt;/code&gt; (which is the default)&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func fetchSharedChanges(_ callback: () -&amp;gt; Void) {
    let changesOperation = CKFetchDatabaseChangesOperation(
        previousServerChangeToken: sharedDBChangeToken  // cached between runs
    )

    // this gives you IDs of changed zones
    changesOperation.recordZoneWithIDChangedBlock = { … }

    // this gives you IDs of deleted zones
    changesOperation.recordZoneWithIDWasDeletedBlock = { … }

    // this gives you the current change token which you need to save
    // may be called multiple times if the operation fetches multiple pages of content
    // save the token each time, so in case of an error you don"t repeat all work
    changesOperation.changeTokenUpdatedBlock = { … }

    changesOperation.fetchDatabaseChangesCompletionBlock = {
        (newToken: CKServerChangeToken?, more: Bool, error: NSError?) -&amp;gt; Void in

        self.sharedDBChangeToken = newToken
        self.fetchZoneChanges(callback)
    }

    self.sharedDB.add(operation)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;fetchZoneChanges&lt;/code&gt; looks very similar, but fetches changes for a specific zone using &lt;code&gt;CKFetchRecordZoneChangesOperation&lt;/code&gt; (you pass it a list of zones)&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;CloudKit best practices:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Automatic authentication:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;CloudKit allows you to authenticate users (if they’re logged in to iCloud) without requiring any private information&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You use the CloudKit user record for authentication&lt;/p&gt;
&lt;p&gt;The user record is unique per container and never changes for that user&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;container.fetchUserRecordID(completionHandler: (CKRecordID?, NSError?) -&amp;gt; Void)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;CKOperation&lt;/code&gt; API:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The convenience API works on single items and it’s simpler to use&lt;/p&gt;
&lt;p&gt;Every convenience API call has a &lt;code&gt;CKOperation&lt;/code&gt; counterpart that lets you perform an operation on a batch of records&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;CKOperation&lt;/code&gt; also has other advantages&amp;nbsp;– for example, it lets you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set up dependencies between operations&lt;/li&gt;
&lt;li&gt;specify quality of service and queue priorities&lt;/li&gt;
&lt;li&gt;cancel operations that have started executing&lt;/li&gt;
&lt;li&gt;specify if you want the operation to work over cellular network&lt;/li&gt;
&lt;li&gt;limit the number of records or set of fetched keys&lt;/li&gt;
&lt;li&gt;report progress&lt;/li&gt;
&lt;li&gt;… and everything that &lt;code&gt;NSOperation&lt;/code&gt; provides&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;(*) watch the &lt;a href="https://developer.apple.com/videos/play/wwdc2015/226/"&gt;Advanced NSOperations talk from 2015&lt;/a&gt; to learn more about &lt;code&gt;NSOperation&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Quality of service:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;QoS: select a quality of service (&lt;code&gt;.userInteractive&lt;/code&gt; / &lt;code&gt;.userInitiated&lt;/code&gt; / &lt;code&gt;.utility&lt;/code&gt; / &lt;code&gt;.background&lt;/code&gt;) depending on the task priority&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;default is &lt;code&gt;.utility&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.utility&lt;/code&gt; and below enable discretionary networking&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Discretionary networking means that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the system decides when is the best moment to run your request, so it may take longer than you expect&lt;/li&gt;
&lt;li&gt;however, all network failures will be automatically retried for you&lt;/li&gt;
&lt;li&gt;the request gets a timeout period of 7 days by default&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Long lived operations:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you have some operations that you want to continue/retry if they don’t manage to complete by the time your app is terminated, iOS 9.3 adds “CloudKit long lived operations”&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Once you run such operation, the system will finish it even if the app is killed by the system or the user&lt;/p&gt;
&lt;p&gt;The request is executed even if your app isn’t running, the result is cached and is returned to you once the app restarts&lt;/p&gt;
&lt;p&gt;Results are kept by the OS for at least 24 hours&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To use this API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set &lt;code&gt;isLongLived = true&lt;/code&gt; on &lt;code&gt;CKOperation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;save the operation’s &lt;code&gt;operationID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;CKContainer.fetchLongLivedOperation(withId:)&lt;/code&gt; to get the operation object back&lt;/li&gt;
&lt;li&gt;set completion blocks and run it again just like a new one&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;CKContainer.default().fetchLongLivedOperation(withID: myOpID) {
    (operation: CKOperation?, error: NSError?) in

    let fetchRecords = operation as! CKFetchRecordsOperation
    fetchRecords.fetchRecordsCompletionBlock = { … }

    CKContainer.default().privateCloudDatabase.add(fetchRecords)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Parent references:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A new type of reference added this year to help you better model data, especially with sharing in mind&lt;/p&gt;
&lt;p&gt;If your app supports sharing, it’s recommended that you set the parent reference to create a hierarchy between records&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Example: Album &amp;nbsp;⭢&amp;nbsp; list of photos&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let photoRecord = CKRecord(recordType: "photo")
photoRecord.setParent(albumRecordID)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;What this gives you: when the user shares the album record, the whole record hierarchy under this album (photos and other data) will also be shared&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Types of errors:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;1) Fatal error (bad request)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Error codes like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.internalError&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.serverRejectedRequest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.invalidArguments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.permissionFailure&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p class="arrow"&gt;→ in this case, you should show an alert to the user and tell them this can’t be executed&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;2) Connection/server error&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Error codes like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.zoneBusy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.serviceUnavailable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.requestRateLimited&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p class="arrow"&gt;→ in this case, check for &lt;code&gt;CKErrorRetryAfterKey&lt;/code&gt; and retry after specified time&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;3) Errors that are returned before connection is even made&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;.networkUnavailable&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you should monitor network reachability (&lt;code&gt;SCNetworkReachability&lt;/code&gt;) and retry when the device is connected again&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;.notAuthenticated&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;when the user is not logged in and can’t access their private database&lt;/li&gt;
&lt;li&gt;you should register at startup for &lt;code&gt;CKAccountChangedNotification&lt;/code&gt;, and when it fires, recheck account status and update the UI&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc15/cloudkit-tips-and-tricks/</id>
    <title>CloudKit Tips and Tricks</title>
    <published>2020-11-11T18:39:56+01:00</published>
    <updated>2020-11-11T18:39:56+01:00</updated>
    <link href="https://mackuba.eu/notes/wwdc15/cloudkit-tips-and-tricks/"/>
    <content type="html">&lt;h3&gt;Error Handling&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Accounts:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To check the account status of the current user:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;container.accountStatusWithCompletionHandler { status, error in … }&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;All APIs that fail because they require an authenticated user return &lt;code&gt;CKErrorNotAuthenticated&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can now subscribe for &lt;code&gt;CKAccountChangedNotification&lt;/code&gt; to be notified when account status changes&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You should avoid showing alerts to the user about a missing account&amp;nbsp;– simply disable parts of the UI that require an account, and reenable them when you get a notification that an account is now available&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Network errors:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Network connection errors that you may sometimes get: &lt;code&gt;CKErrorNetworkFailure&lt;/code&gt;, &lt;code&gt;CKErrorServiceUnavailable&lt;/code&gt;, &lt;code&gt;CKErrorZoneBusy&lt;/code&gt;, &lt;code&gt;CKErrorRequestRateLimited&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;These errors include a key &lt;code&gt;CKErrorRetryAfterKey&lt;/code&gt; in their user info dictionary that tells you how long you should wait before retrying&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Handling conflicts:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you try to save a record that has been modified in the meantime on the server (meaning: the record change tag you’re sending with the save request is outdated), you will receive the error &lt;code&gt;CKErrorServerRecordChanged&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;There is no magic happening behind the scenes in such case, iCloud doesn’t make assumptions about how you want to resolve conflicts, you need to handle this yourself&lt;/p&gt;
&lt;p&gt;However, the SDK provides you the necessary information in the &lt;code&gt;userInfo:&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CKRecordChangedErrorClientRecordKey&lt;/code&gt;&amp;nbsp;– what you tried to save&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CKRecordChangedErrorAncestorRecordKey&lt;/code&gt;&amp;nbsp;– the original version&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CKRecordChangedErrorServerRecordKey&lt;/code&gt;&amp;nbsp;– what is currently on the server&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Usually you will want to resolve the conflict by applying the same changes that you did on the original record to the current server version of the record&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;CloudKit Operations&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Batch operations:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you create and save a lot of records in one go, each of them will create a separate network request, and making a lot of requests in a short period means you’re likely to hit some kind of rate limit and they will be queued up&lt;/p&gt;
&lt;p&gt;To avoid making multiple similar requests, you can use the &lt;code&gt;CKOperation&lt;/code&gt; API&lt;/p&gt;
&lt;p&gt;Almost every convenience API method that works on one record at a time has a &lt;code&gt;CKOperation&lt;/code&gt; counterpart that allows you to work on a batch of records&lt;/p&gt;
&lt;p&gt;For saving multiple records, use &lt;code&gt;CKModifyRecordsOperation&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;class CKModifyRecordsOperation: CKDatabaseOperation {
  convenience init(recordsToSave: [CKRecord]?, recordIDsToDelete: [CKRecordID]?)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Note: there are certain limits on how large batch operations you can make (number of items in the request and total request size)&lt;/p&gt;
&lt;p&gt;This doesn’t include the size of saved binary assets, just the record field data&lt;/p&gt;
&lt;p&gt;If you hit this limit, you will get the &lt;code&gt;CKErrorLimitExceeded&lt;/code&gt; error&lt;/p&gt;
&lt;p&gt;In that case, the best solution is usually to try to divide the batch in half and make two requests&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If one or more records in the batch can’t be saved, you will get &lt;code&gt;CKErrorPartialFailure&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;From this error’s &lt;code&gt;userInfo&lt;/code&gt; you can get a dictionary with specific record errors under &lt;code&gt;CKPartialErrorsByItemIDKey&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;In a standard zone, in such scenario some records will be saved and those with errors won’t&lt;/p&gt;
&lt;p&gt;In a custom zone you can make an atomic update&amp;nbsp;– in this case, in case of a problem with some of the records, no records will actually be saved, but instead you will get an error &lt;code&gt;CKErrorBatchRequestFailed&lt;/code&gt; for those that could have been saved but weren’t&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Queries:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you expect a query to return a large number of records, but you only need a small number of them at a time, you can use the &lt;code&gt;CKQueryOperation.resultsLimit&lt;/code&gt; property&lt;/p&gt;
&lt;p&gt;Also available on &lt;code&gt;CKFetchRecordChangesOperation&lt;/code&gt;, &lt;code&gt;CKFetchNotificationChangesOperation&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;When limiting the number of records, you will usually also want to set the query's &lt;code&gt;sortDescriptors&lt;/code&gt; to e.g. sort records by oldest or newest first&lt;/p&gt;
&lt;p&gt;You can use the &lt;code&gt;creationDate&lt;/code&gt; key which is automatically added to all saved records regardless of type&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To implement pagination and get further pages beyond the first one, use the &lt;code&gt;CKQueryCursor&lt;/code&gt; object that you get in response to the &lt;code&gt;queryCompletionBlock&lt;/code&gt; callback&lt;/p&gt;
&lt;p&gt;Then, initialize the next &lt;code&gt;CKOperation&lt;/code&gt; passing it the cursor object in the argument to the initializer&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you don’t need all information contained in a record immediately, you can also use the &lt;code&gt;desiredKeys&lt;/code&gt; property to only download the keys you want&lt;/p&gt;
&lt;p&gt;E.g. download the record’s thumbnail image but not a full-size photo&lt;/p&gt;
&lt;p&gt;Also available on &lt;code&gt;CKFetchRecordsOperation&lt;/code&gt;, &lt;code&gt;CKFetchRecordChangesOperation&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Maintaining a local cache:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Let's say you want to keep some subset of all data completely cached on all local devices for quicker access (e.g. user’s personal notes)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You have two options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;CKQueryOperation&lt;/code&gt; to fetch all records and synchronize them manually&lt;/li&gt;
&lt;li&gt;make a custom zone and use delta downloads using &lt;code&gt;CKFetchRecordChangesOperation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;When saving fetched records to a local database, you should save CKRecord’s system fields like the change tag together with your own data&lt;/p&gt;
&lt;p&gt;To do that, you can use &lt;code&gt;encodeSystemFieldsWithCoder:&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWithMutableData: data)
archiver.requiresSecureCoding = true
record.encodeSystemFieldsWithCoder(archiver)
archiver.finishEncoding()&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;When restoring a record from the local storage, you don’t have to set all its data fields&amp;nbsp;– it’s fine to only set those you want to change&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To synchronize any changes from the server, create a subscription subscribing to a given record type using silent notifications, and use &lt;code&gt;CKFetchRecordChangesOperation&lt;/code&gt; to fetch all recent changes when notified&lt;/p&gt;
&lt;p&gt;Subscriptions (&lt;code&gt;CKSubscription&lt;/code&gt;) are persistent queries on the server that send remote notifications about a relevant change&amp;nbsp;– either in a specific record set (query subscription) or in the whole zone (zone subscription)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To get CloudKit subscription notifications, you need to follow the usual setup for push notifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;have push notification capability enabled for your app&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;registerForRemoteNotifications()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;registerUserNotificationSettings(…)&lt;/code&gt; if you want to show notifications to the user&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To ask for silent subscription notifications, configure the &lt;code&gt;CKNotificationInfo&lt;/code&gt; object appropriately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set the &lt;code&gt;shouldSendContentAvailable&lt;/code&gt; key&lt;/li&gt;
&lt;li&gt;do not set any of the UI-related keys: &lt;code&gt;alertBody&lt;/code&gt;, &lt;code&gt;shouldBadge&lt;/code&gt;, &lt;code&gt;soundName&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Notification priorities: a notification is high priority if it has any UI keys set, otherwise it’s medium priority&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;For silent notifications, the &lt;code&gt;UIApplicationDelegate&lt;/code&gt; will receive the following callback:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;func application(application: UIApplication,
	didReceiveRemoteNotification: [NSObject: AnyObject],
	fetchCompletionHandler: (UIBackgroundFetchResult) -&amp;gt; Void) { … }&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Remember that push notification delivery in general is “best effort”&amp;nbsp;– pushes can be dropped if many are received in a short period of time or because of network issues&lt;/p&gt;
&lt;p&gt;Silent notifications may also be additionally delayed if the system is waiting for better conditions&lt;/p&gt;
&lt;p&gt;When you receive a notification, use &lt;code&gt;CKFetchNotificationChangesOperation&lt;/code&gt; to check the server’s notification collection for any notifications you might have missed&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You may want to use a &lt;code&gt;UIApplication&lt;/code&gt; background task (&lt;code&gt;beginBackgroundTaskWithName(…)&lt;/code&gt;) for syncing tasks&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Interactive notifications: you can now make CloudKit notifications interactive (e.g. show action buttons) by setting the &lt;code&gt;category&lt;/code&gt; key on &lt;code&gt;CKNotificationInfo&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Other performance tips&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;CloudKit is a highly asynchronous API, most operations require a network call and take some time to execute&lt;/p&gt;
&lt;p&gt;You will often want to make a series of operations that have some dependencies between them&lt;/p&gt;
&lt;p&gt;Things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;however you implement task handling, remember to always handle all errors&lt;/li&gt;
&lt;li&gt;never block the main thread with an operation in progress&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Don’t nest calls to the convenience API methods, creating a “callback hell”&lt;/p&gt;
&lt;p&gt;Don’t use locks/semaphores to wait for an API call to finish&lt;/p&gt;
&lt;p&gt;Instead, use the &lt;code&gt;addDependency()&lt;/code&gt; API in &lt;code&gt;CKOperation&lt;/code&gt; to add dependencies between operations:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let firstFetch = CKFetchRecordsOperation(…)
let secondFetch = CKFetchRecordsOperation(…)
secondFetch.addDependency(firstFetch)

let queue = NSOperationQueue()
queue.addOperations([firstFetch, secondFetch], waitUntilFinished: false)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use the &lt;code&gt;qualityOfService&lt;/code&gt; property on &lt;code&gt;NSOperation&lt;/code&gt; to indicate which operations are something you need in the UI and which are low priority background operations&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;there used to be a &lt;code&gt;usesBackgroundSession&lt;/code&gt; property on &lt;code&gt;CKOperation&lt;/code&gt; too, but it’s deprecated now &amp;nbsp;⭢&amp;nbsp; use quality of service for this&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;QoS = &lt;code&gt;.utility&lt;/code&gt; and &lt;code&gt;.background&lt;/code&gt; use discretionary networking, use &lt;code&gt;.userInteractive&lt;/code&gt; and &lt;code&gt;.userInitiated&lt;/code&gt; for high priority tasks&lt;/p&gt;
&lt;p&gt;Note: &lt;code&gt;.background&lt;/code&gt; QoS is the default if you don’t change it!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;update:&lt;/strong&gt; now it's &lt;code&gt;.utility&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc14/advanced-cloudkit/</id>
    <title>Advanced CloudKit</title>
    <published>2020-11-03T14:57:14+01:00</published>
    <updated>2020-11-03T14:57:14+01:00</updated>
    <link href="https://mackuba.eu/notes/wwdc14/advanced-cloudkit/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;CloudKit API is designed to be asynchronous, all calls return through a callback, because they all require a network connection&lt;/p&gt;
&lt;p&gt;The main API (“operational API”) is based on &lt;code&gt;NSOperation&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You use it by creating special &lt;code&gt;NSOperation&lt;/code&gt; objects for a given use case, e.g. &lt;code&gt;CKFetchRecordsOperation&lt;/code&gt;, and specifying parameters and callbacks in their properties&lt;/p&gt;
&lt;p&gt;Apart from the final result callback, you can set callbacks e.g. for reporting download progress or to get records one by one as they’re downloaded&lt;/p&gt;
&lt;p&gt;Operation lifecycle (cancelling, suspending etc.) can be managed through standard &lt;code&gt;NSOperation&lt;/code&gt; methods and &lt;code&gt;NSOperationQueue&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are separete fetch/modify operation types for records, subscriptions, zones, users and notifications&lt;/p&gt;
&lt;p&gt;You can set dependencies between operations (also if they’re in different queues), e.g. make a fetch operation and then a modify operation that needs to wait for the object to load&lt;/p&gt;
&lt;p&gt;Operations can also have different priority levels&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Starting an operation:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;(ℹ️ Note: this wasn’t in the video, but it really should have been, because it's completely not obvious.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;How to start an operation once you prepare the &lt;code&gt;CKOperation&lt;/code&gt; object:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;1) Use the database’s built-in queue:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let fetchOperation = ...
CKContainer.default().privateCloudDatabase.add(fetchOperation)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;2) Use your own operation queue and assign a reference to the database:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let operationQueue = NSOperationQueue()

let fetchOperation = ...
fetchOperation.database = CKContainer.default().privateCloudDatabase
operationQueue.addOperation(fetchOperation)&lt;/pre&gt;
&lt;h3&gt;Custom zones&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Custom zones (in the private database) let you compartmentalize data and add some special features&lt;/p&gt;
&lt;p&gt;Records can’t be moved between zones or have cross-zone relationships&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are some operations that can only be done in custom zones:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Atomic commits:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Objects in the CloudKit database have relationships between them, and you want to keep all data consistent&lt;/p&gt;
&lt;p&gt;Atomic commits are kind of like transactions in a relational database: batch operations succeed or fail together&lt;/p&gt;
&lt;p&gt;Only available in the private database (because public database may be accessed by millions of users at the same time)&lt;/p&gt;
&lt;p&gt;If an operation fails, you get a &lt;code&gt;CKErrorPartialFailure&lt;/code&gt; response, with the user info containing info about errors on specific records (&lt;code&gt;CKPartialErrorsByItemID&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Error &lt;code&gt;CKErrorBatchRequestFailed&lt;/code&gt; means that this record wasn’t saved because of a problem with another record in the batch&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Delta downloads:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Allow you to download a list of all changes since the last time the app was online, to let you perform a full sync&lt;/p&gt;
&lt;p&gt;When a device connects, you can send a “change token” to the server asking for all changes since that version&lt;/p&gt;
&lt;p&gt;This lets you implement an offline cache of the whole dataset and sync any changes when possible&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To do that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;track all local changes&lt;/li&gt;
&lt;li&gt;send changes to the server when connected&lt;/li&gt;
&lt;li&gt;resolve conflicts&lt;/li&gt;
&lt;li&gt;fetch server changes with &lt;code&gt;CKFetchRecordChangesOperation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;remember the received new server change token and send it back next time&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Zone subscriptions:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Let you subscribe for notifications about any change in the zone&lt;/p&gt;
&lt;p&gt;When you get a notification, you request a delta download&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Advanced record operations&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Record changes:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;When you change some fields in a &lt;code&gt;CKRecord&lt;/code&gt;, the changes are automatically tracked locally and only the changed fields are transmitted when you save it&lt;/p&gt;
&lt;p&gt;By default CloudKit performs a “locked update”, which makes sure that the update is only saved on the server if the record wasn’t modified in the meantime by another client (this uses record change tokens)&lt;/p&gt;
&lt;p&gt;After you execute a save, the server returns your record with a new change token&amp;nbsp;– so you should use that returned version for any subsequent changes&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Unlocked update &amp;nbsp;⭢&amp;nbsp; just overwrites server data regardless what is there&lt;/p&gt;
&lt;p&gt;Locked update &amp;nbsp;⭢&amp;nbsp; if the record was changed in the meantime, you get back an error (&lt;code&gt;CKErrorServerRecordChanged&lt;/code&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;userInfo&lt;/code&gt; of the &lt;code&gt;CKErrorServerRecordChanged&lt;/code&gt; error contains info that lets you perform a 3-way merge:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CKRecordChangedErrorClientRecordKey&lt;/code&gt;&amp;nbsp;– what you tried to save&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CKRecordChangedErrorAncestorRecordKey&lt;/code&gt;&amp;nbsp;– the original version&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CKRecordChangedErrorServerRecordKey&lt;/code&gt;&amp;nbsp;– what is currently on the server&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Based on the values from these 3 copies of the record you can decide what state the record should be in, and then retry the save&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can modify the behavior with “save policies”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SaveIfServerUnchanged&lt;/code&gt; &amp;nbsp;⭢&amp;nbsp; default, performs a locked update and sends only changed keys&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SaveChangedKeys&lt;/code&gt; &amp;nbsp;⭢&amp;nbsp; unlocked update, sends only changed keys&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SaveAllKeys&lt;/code&gt; &amp;nbsp;⭢&amp;nbsp; unlocked update, overwrites all keys in the record (note: this doesn’t affect keys that aren’t present in the local copy at all)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You should almost always use the default locked update (&lt;code&gt;SaveIfServerUnchanged&lt;/code&gt;), use unlocked updates only to forcefully resolve serious conflicts&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;SaveAllKeys&lt;/code&gt; if the user requests to overwrite server data with local data&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Partial records:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The &lt;code&gt;desiredKeys&lt;/code&gt; field present in most operation types lets you specify that you only want to download selected keys from the server&lt;/p&gt;
&lt;p&gt;This is useful if the whole record is very large and you don’t need all of it&lt;/p&gt;
&lt;p&gt;Partial records can be normally saved after a change&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;CloudKit data modeling&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Forward reference &amp;nbsp;⭢&amp;nbsp; a parent object keeps an array of references to children in its property&lt;/p&gt;
&lt;p&gt;Backward reference &amp;nbsp;⭢&amp;nbsp; only child objects have a reference to the parent&lt;/p&gt;
&lt;p&gt;It’s recommended to use backward references&amp;nbsp;– with a forward reference you need to update the parent object every time a new child is added, and you will run into conflicts if multiple clients are adding records&lt;/p&gt;
&lt;p&gt;To get a list of all children using backward references, make a query for all child records with a predicate “owner = X”&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;References give you cascading deletes&amp;nbsp;– when you delete the parent object, all child objects and their children are deleted&lt;/p&gt;
&lt;p&gt;If an object has two parent references, it’s deleted when the first parent is deleted&lt;/p&gt;
&lt;p&gt;When batch uploading a tree of objects, CloudKit makes sure that parent objects are uploaded first so that you don’t get inconsistent data during upload (important in the public database)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Your data objects:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;CloudKit is only a transport mechanism and requires you to keep and manage your own local copy of all data&lt;/p&gt;
&lt;p&gt;It’s recommended that you don’t subclass &lt;code&gt;CK*&lt;/code&gt; objects to build your models&amp;nbsp;– make your own completely independent model classes and translate to/from CloudKit objects when fetching and saving&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Handling push notifications:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You need to remember that push notifications in general aren’t guaranteed to be delivered&lt;/p&gt;
&lt;p&gt;The server only stores one push per client, so if you reconnect e.g. after a flight, you might miss some previous notifications&lt;/p&gt;
&lt;p&gt;You can find pushes that you’ve missed in a “Notification Collection” where every notification is saved&lt;/p&gt;
&lt;p&gt;The Notification Collection works kind of like delta updates&amp;nbsp;– you ask for notifications since a given change token and you get a list of everything added since then&lt;/p&gt;
&lt;p&gt;You can mark a notification as read, which notifies all other clients that they can ignore it&lt;/p&gt;
&lt;p&gt;You should check the Notification Collection every time you get a push, since you never know what you might have missed (this doesn’t only happen with airplane mode)&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;The iCloud Dashboard&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;The dashboard lets you browse data saved by your app&amp;nbsp;– the whole public database and the private database for your developer account (but not anyone else’s private database)&lt;/p&gt;
&lt;p&gt;You can view saved records, run queries with any filters, and add new records&lt;/p&gt;
&lt;p&gt;You can define roles in the public database and define for each model who can create/read/modify records (e.g. specify that records are publicly readable but only an admin can create them)&lt;/p&gt;
&lt;p&gt;You will also see a list of all user ids and first/last names of those users that marked themselves as discoverable&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Schema:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The CloudKit database has two separate “environments”: development and production&lt;/p&gt;
&lt;p&gt;The schema for each record type is “just in time” during development, i.e. when you save a new type of record, it automatically creates a new schema for that record type, recording every field type, and when you save a record with a new field, it adds a field to the list&lt;/p&gt;
&lt;p&gt;However, once you’re ready to release a new version of your app, you need to save the schema to production and at that point it’s locked&amp;nbsp;– a production version of the app can’t save records or fields that aren’t defined in the schema&lt;/p&gt;
&lt;p&gt;CloudKit also automatically creates indexes for each field in each record type&amp;nbsp;– when you’re done with development, you can delete some indexes that you won’t need so they don’t waste space in the production database&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Tips &amp; tricks&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Please handle all errors&amp;nbsp;:)&lt;/p&gt;
&lt;p&gt;Remember that you can get partial errors (when atomic commits aren’t used), so some records might be saved while others aren’t&lt;/p&gt;
&lt;p&gt;Retry any “server busy” errors (&lt;code&gt;CKErrorRetryAfterKey&lt;/code&gt; tells you the amount of time you should wait)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Don’t waste space in your users’ iCloud in private databases, they may be paying real money for it&lt;/p&gt;
&lt;p&gt;Limits in the public database are mostly to prevent abuse, they should be fine for most normal use (the limits scale with the number of users)&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc12/icloud-storage-overview/</id>
    <title>iCloud Storage Overview</title>
    <published>2020-11-01T20:52:45+01:00</published>
    <updated>2020-11-01T20:52:45+01:00</updated>
    <link href="https://mackuba.eu/notes/wwdc12/icloud-storage-overview/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;iCloud Storage APIs allow you to store your app’s data in iCloud&lt;/p&gt;
&lt;p&gt;System services sync your data automatically even when your app isn’t running&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;3 different types of storage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;key-value storage&amp;nbsp;– simple storage for things like preferences, game state (“it’s so simple, we actually don’t have another session talking about it”)&lt;/li&gt;
&lt;li&gt;document storage&amp;nbsp;– a filesystem in the cloud scoped for your application, where you can store any kinds of files and folders, synced between devices; ideal for productivity apps like iWork&lt;/li&gt;
&lt;li&gt;Core Data storage&amp;nbsp;– an extension for Core Data that lets you store Core Data databases in the cloud [note: deprecated in 2016]&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;What iCloud handles for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;account setup&amp;nbsp;– users don’t need to create a new account for your service, they already have an iCloud account&lt;/li&gt;
&lt;li&gt;APIs for your apps integrated into the OSX/iOS SDKs&lt;/li&gt;
&lt;li&gt;server code you don’t have to write, for things like load balancing, replication, backup and recovery&lt;/li&gt;
&lt;li&gt;the personnel handling the servers, support etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;em&gt;“If you happen to know some friends, who like writing server code that scales to hundreds of millions of users, send them our way&amp;nbsp;– we’re hiring”&lt;/em&gt; :D&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;How it works:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Key-value storage:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Accessed through &lt;code&gt;NSUbiquitousKeyValueStore&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Lets you put simple plist-type values into the cloud&lt;/p&gt;
&lt;p&gt;It talks to a key-value service running on the device which talks to iCloud on your behalf&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Document storage:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use &lt;code&gt;UIDocument&lt;/code&gt; (iOS) / &lt;code&gt;NSDocument&lt;/code&gt; (OSX) / &lt;code&gt;UIManagedDocument&lt;/code&gt; (Core Data)&lt;/p&gt;
&lt;p&gt;You can also use lower level file storage APIs like &lt;code&gt;NSFileCoordination&lt;/code&gt;, &lt;code&gt;NSFilePresenter&lt;/code&gt;, &lt;code&gt;NSMetadataQuery&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;All these APIs (also the Core Data iCloud API) talk to OS’s document service which talks to the iCloud for you&lt;/p&gt;
&lt;p&gt;You never actually interact with the iCloud servers yourself, you just use these APIs in the SDK and system services&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;How to enable iCloud in your project:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;ul&gt;
&lt;li&gt;your app needs to be distributed through the App Store [note: no longer true for Mac apps]&lt;/li&gt;
&lt;li&gt;enable the relevant entitlement: &lt;code&gt;com.apple.developer.ubiquity-kvstore-identifier&lt;/code&gt; and/or &lt;code&gt;com.apple.developer.ubiquity-container-identifiers&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h3&gt;Working with Key Value Storage:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NSUbiquitousKeyValueStore&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Lets you store simple plist values&amp;nbsp;– strings, numbers, booleans, dictionaries and arrays of those&lt;/p&gt;
&lt;p&gt;Similar API to &lt;code&gt;UserDefaults&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Simple conflict resolution: if two devices independently change or add a value for a given key, the latest change wins&lt;/p&gt;
&lt;p&gt;Works even if iCloud isn’t configured&amp;nbsp;– in this case it just acts as local user defaults that don’t sync&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Improvements from last year’s initial release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;increased capacity&amp;nbsp;– now up to 1024 keys, up to 1 MB per application (not part of the total user quota); it’s fine to have just one key of 1 MB&lt;/li&gt;
&lt;li&gt;improved responsiveness (allows around 15 requests every 90 seconds)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;How to set up:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;// get a reference to the store
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];

// observe changes
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
           selector:@selector(kvStoreDidChange:)
               name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
             object:nil];

// ask for any changes since the last launch
[store synchronize];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Making changes:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;[store setObject:someObject forKey:@"someKey"];
[store setBool:YES forKey:@"someOtherKey"];

[store synchronize];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;It’s recommended to also store another copy of the same data locally, so that you can do conflict resolution manually in case if just keeping the latest change isn’t always the right strategy for your app&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Handling notifications about a change:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;- (void)kvStoreDidChange:(NSNotification *)notification {
    NSDictionary *userInfo = [notification userInfo];

    // get change reason
    int reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
    NSArray *changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];

    // … store values locally, do conflict resolution etc.
}&lt;/pre&gt;
&lt;h3&gt;Working with document storage:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Apart from your app’s container, each app has a separate “ubiquity container” (or iCloud container)&lt;/p&gt;
&lt;p&gt;The app can put any files inside that container, and whatever is put there is synced with other devices via iCloud, kind of like Dropbox&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;When you put a file in the ubiquity container, the file is broken into chunks and the chunks that are new or modified are uploaded to iCloud&amp;nbsp;– so if you only change a few bytes of the file, most of it doesn’t need to be uploaded again&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Metadata about the file is uploaded first&amp;nbsp;– info about the file name, type, size etc.&lt;/p&gt;
&lt;p&gt;So every device knows about each file that’s uploaded, but it doesn’t necessarily have to download each file&amp;nbsp;– the decision is made independently on the device depending on the platform and settings: OSX usually downloads all files if it has space, iOS only downloads files on demand&lt;/p&gt;
&lt;p&gt;Once a file is downloaded to the device, all later changes are also automatically synced&lt;/p&gt;
&lt;p&gt;Local peer to peer communication is used if possible&amp;nbsp;– e.g. if you have a file on the Mac, your iPhone will copy it from the Mac instead of the iCloud network&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Automatic conflict resolution&amp;nbsp;– if a file is edited on two devices in parallel, the system picks a winner automatically, but your application gets access to both versions and can override it if needed&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;URL publishing: you can make the current version of the document public and available through a URL which you can share with others (if it’s changed later in the iCloud, the URL still downloads that previous version)&lt;/p&gt;
&lt;p&gt;URLs are not permanent, they expire after some time&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Detecting an iCloud account:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Unlike key-value storage, using this API requires that the user has an iCloud account&lt;/p&gt;
&lt;p&gt;You can check for an “Ubiquity Identity Token” to see if they have an account configured&lt;/p&gt;
&lt;p&gt;The token is anonymous so it doesn’t tell you anything about the user, but it will change if the user switches to another account (you also get a notification then)&lt;/p&gt;
&lt;p&gt;The token is also unique to your app and to this specific device, so the same app will get a different token from that user on another device&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;id token = [[NSFileManager defaultManager] ubiquityIdentityToken];
if (token) {
    // cache the token
    // the next time the app launches, check if it has changed
}

// register for the identity changed notification
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
           selector:@selector(handleUbiquityIdentityChanged:)
               name:NSUbiquityIdentityDidChangeNotification
             object:nil];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;When the account changes, clear any local caches specific to this account and refresh the UI&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To access the ubiquity container, you ask for the container URL&amp;nbsp;– note that the container will be created on demand the first time you ask for it; this should ideally not be called on the main thread&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    containerURL = [fileManager URLForUbiquityContainerIdentifier:nil];
});&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Types of documents you can store in the document storage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;normal files, also symlinks&lt;/li&gt;
&lt;li&gt;directories of files&lt;/li&gt;
&lt;li&gt;packages&amp;nbsp;– bundles of files that act as a single document&lt;/li&gt;
&lt;li&gt;Core Data stores&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;File extended attributes are also synced&lt;/p&gt;
&lt;p&gt;Watch out for filesystem case sensitivity issues&amp;nbsp;– users running on a Mac might have a case-sensitive filesystem&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;For packages, iCloud updates only the files from a package that have been changed, but it handles updates to the whole package atomically, so you will not get a package in an inconsistent state&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Core Data:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Useful for so-called “shoebox style applications”, like iPhoto or iTunes, which work with a single database in app’s own format&lt;/p&gt;
&lt;p&gt;The Core Data store remains local and only change logs are uploaded to iCloud&lt;/p&gt;
&lt;p&gt;Not recommended to use binary and XML stores, because in those cases every change modifies the whole file (only use those for small data sets that don’t change often)&amp;nbsp;– use SQLite stores for iCloud sync instead&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;UIManagedDocument&lt;/code&gt;&amp;nbsp;– a subclass of &lt;code&gt;UIDocument&lt;/code&gt; for managing Core Data stores that supports syncing them with iCloud&lt;/p&gt;
&lt;p&gt;Note: &lt;code&gt;NSPersistentDocument&lt;/code&gt;, the AppKit equivalent, does not support iCloud&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Designing your document format for iCloud:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Design with network efficiency in mind&amp;nbsp;– don’t write a lot of changes very often&lt;/p&gt;
&lt;p&gt;Keep in mind any possible differences between platforms&lt;/p&gt;
&lt;p&gt;Plan for future app upgrades&amp;nbsp;– include version number in the format and keep compatibility if possible&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Beware of sync loops&amp;nbsp;– when one instance of the app receives a change, merges it and writes back the result, and the other side does the same and triggers a change in the first copy again&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“And you have two versions of the app playing ping-pong with the user’s iCloud account”&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Avoid making rapid changes to the file, e.g. updating some position tag while the user is scrolling the document&lt;/p&gt;
&lt;p&gt;Don’t put the last open date into the document itself, so that opening it doesn’t count as making a change&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use iCloud for user data only: don’t put any caches, temporary files or auto-generated content there&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Think about privacy when allowing the user to publish a document: don’t include any sensitive info or things they might not be aware they’re publishing (e.g. undo history) in the publicly accessible view&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;APIs:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NSFileManager&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NSFileCoordinator&lt;/code&gt; &amp;amp; &lt;code&gt;NSFilePresenter&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NSMetadataQuery&lt;/code&gt;&amp;nbsp;– use a live metadata query to be notified of new files and changes before the file contents are downloaded&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NSDocument&lt;/code&gt; &amp;amp; &lt;code&gt;UIDocument&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UIManagedDocument&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;NSDocument/UIDocument&lt;/code&gt; handle most of the integration with iCloud for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;using the ubiquity container&lt;/li&gt;
&lt;li&gt;coordinating with the OS&lt;/li&gt;
&lt;li&gt;tracking files and their versions&lt;/li&gt;
&lt;li&gt;resolving conflicts&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Tips:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;ul&gt;
&lt;li&gt;subclass native document types&lt;/li&gt;
&lt;li&gt;use the default auto-save behavior&lt;/li&gt;
&lt;li&gt;if the app provides a prepopulated Core Data store on first launch, use a migration instead of copying a packaged store file&lt;/li&gt;
&lt;li&gt;track documents using &lt;code&gt;NSMetadataQuery&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;if it makes sense for your app, manually control conflict resolution (&lt;code&gt;UIDocumentStateInConflict&lt;/code&gt;); avoid user involvement if possible&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Tips for debugging:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;ul&gt;
&lt;li&gt;test with multiple devices&lt;/li&gt;
&lt;li&gt;monitor network traffic&lt;/li&gt;
&lt;li&gt;use Airplane Mode to create conflicts artificially&lt;/li&gt;
&lt;li&gt;there will be a configuration profile available that makes the document service log additional messages&lt;/li&gt;
&lt;li&gt;&lt;code&gt;developer.icloud.com&lt;/code&gt;&amp;nbsp;– a web tool that shows you all iCloud storage on your account from various apps [note: not available anymore, replaced with CloudKit dashboard, but it only shows CloudKit data]&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc14/introducing-cloudkit/</id>
    <title>Introducing CloudKit</title>
    <published>2020-11-01T20:52:04+01:00</published>
    <updated>2020-11-01T20:52:04+01:00</updated>
    <link href="https://mackuba.eu/notes/wwdc14/introducing-cloudkit/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;CloudKit lets you write client applications without having to build and host a server part to handle things like database, accounts or push notifications&lt;/p&gt;
&lt;p&gt;Usage is free for the developer up to pretty big limits&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;CloudKit gives you more direct access to iCloud servers&lt;/p&gt;
&lt;p&gt;It’s the framework that’s used behind the scenes by iCloud Photo Library and iCloud Drive&lt;/p&gt;
&lt;p&gt;Uses the same iCloud account as iCloud documents or key-value storage&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Two types of databases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public&amp;nbsp;– accessible to everyone&lt;/li&gt;
&lt;li&gt;private&amp;nbsp;– private data of a specific user&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;It’s only a transport technology, it does not deal with local data persistence&amp;nbsp;– you need to decide how you store the data that you load from the cloud&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;To enable iCloud in your app, set it up in the Capabilities tab in Xcode just like with other iCloud APIs&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Containers:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Each app’s data is kept in a separate container (&lt;code&gt;CKContainer&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Containers give you the safety that your app’s data will not be mixed with someone else’s app’s data&lt;/p&gt;
&lt;p&gt;A container’s ID needs to be unique in the whole iCloud, so use reverse-domain style identifiers&lt;/p&gt;
&lt;p&gt;By default each app has one container of its own, but apps can additionally use shared containers&lt;/p&gt;
&lt;p&gt;Containers are managed by the developer through the WWDR portal&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Databases:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A container contains one shared public database for everyone, and separate private databases for each user&lt;/p&gt;
&lt;p&gt;An app running on the device has access to one public and one private database (&lt;code&gt;CKDatabase&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;The database is the initial entry point to CloudKit (from a container)&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
CKDatabase *privateDatabase = [[CKContainer defaultContainer] privateCloudDatabase];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Private database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;requires a logged in iCloud account&lt;/li&gt;
&lt;li&gt;data stored counts against the user’s iCloud account quota&lt;/li&gt;
&lt;li&gt;default permission for data is user readable&lt;/li&gt;
&lt;li&gt;the data your users store in your app’s CloudKit container is *not* accessible to you&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Public database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;can be accessed anonymously even if the user isn’t logged in&lt;/li&gt;
&lt;li&gt;data stored counts against the developer’s app quota&lt;/li&gt;
&lt;li&gt;default permission for data is world readable&lt;/li&gt;
&lt;li&gt;permissions can be customized using iCloud Dashboard Roles&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Records:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A record (&lt;code&gt;CKRecord&lt;/code&gt;) is a single “object” in the CloudKit database, essentially a list of key-value pairs&lt;/p&gt;
&lt;p&gt;Records have a Record Type (~ table name)&lt;/p&gt;
&lt;p&gt;There is no defined up front schema, you can just save a record of any type with any keys and the schema will be updated based on that&lt;/p&gt;
&lt;p class="arrow"&gt;→ note: this only works in development, the schema is fixed in production, see &lt;a href="/notes/wwdc14/advanced-cloudkit/"&gt;"Advanced CloudKit"&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Records can have metadata: who created it &amp;amp; modified it and when, also includes a “change tag” (version id)&amp;nbsp;– for determining if two sides have the same version of a record&lt;/p&gt;
&lt;p&gt;Record values can be: strings, numbers, dates, &lt;code&gt;NSData&lt;/code&gt;, &lt;code&gt;CLLocation&lt;/code&gt;, &lt;code&gt;CKReference&lt;/code&gt;, &lt;code&gt;CKAsset&lt;/code&gt;, arrays of any of these&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;- (instancetype)initWithRecordType:(NSString *)recordType;
- (id)objectForKey:(NSString*)key;
- (void)setObject:(id)object forKey:(NSString *)key;
- (NSArray *)allKeys;&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Subscripts also work:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;CKRecord *party = [[CKRecord alloc] initWithRecordType:@"Party"];
party[@"start"] = [NSDate date];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Record zones:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Records are grouped within a database inside “zones” (&lt;code&gt;CKRecordZoneID&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;The public database has one zone, the private database has one default zone, but it can have additional custom zones&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Record identifiers:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Record identifier (&lt;code&gt;CKRecordID&lt;/code&gt;) is a tuple grouping: a “record name” + zone ID&lt;/p&gt;
&lt;p&gt;You can provide a &lt;code&gt;recordID&lt;/code&gt; when creating a record instance&lt;/p&gt;
&lt;p&gt;If you don’t provide a &lt;code&gt;recordID&lt;/code&gt;, a random UUID will be assigned&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A reference (&lt;code&gt;CKReference&lt;/code&gt;) is a pointer from one record to another, as an id of the “parent” record contained in a child record’s field&lt;/p&gt;
&lt;p&gt;References allow you to do cascade deletes, deleting child records when parent is deleted&lt;/p&gt;
&lt;p&gt;You can create a reference from a &lt;code&gt;CKRecord&lt;/code&gt; object or from a &lt;code&gt;CKRecordID&lt;/code&gt; if you know the ID but don’t have the object in memory&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Assets:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;An asset (&lt;code&gt;CKAsset&lt;/code&gt;) is an unstructured piece of data, basically a binary file&lt;/p&gt;
&lt;p&gt;Assets are downloaded and uploaded from/to files on disk, not from memory&lt;/p&gt;
&lt;p&gt;An asset is always owned by a record, and is deleted when the record is deleted&lt;/p&gt;
&lt;p&gt;Transport of assets is optimized so that only the minimal amount of data is transferred&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;APIs:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;There are two different APIs for managing CloudKit data: “operational API” and “convenience API”&lt;/p&gt;
&lt;p&gt;The operational API has every possible operation you might need, the convenience API is more convenient&lt;/p&gt;
&lt;p&gt;Start with the convenience API, use operational API for tweaking and overriding options if needed&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;CloudKit APIs for saving/fetching data are asynchronous&amp;nbsp;– there is no SDK-managed local data, everything needs to go over the network unless you manually cache it&lt;/p&gt;
&lt;p&gt;In CloudKit it’s absolutely necessary to properly handle error cases&amp;nbsp;– every network call can fail and your app needs to be prepared for this&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Convenience API:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;[publicDatabase saveRecord:obj completionHandler: { … }];
[publicDatabase retchRecordWithID:recordID completionHandler: { … }];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Queries:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;For any large database, or the shared public database, you shouldn’t try to keep a copy of the whole database on disk and sync all of it, but instead fetch what you need on demand&amp;nbsp;– for this you can use queries&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;A query (&lt;code&gt;CKQuery&lt;/code&gt;) allows you to fetch a list of records matching some conditions&lt;/p&gt;
&lt;p&gt;Query can specify a &lt;code&gt;RecordType&lt;/code&gt;, &lt;code&gt;NSPredicate&lt;/code&gt; and optionally &lt;code&gt;NSSortDescriptors&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A subset of &lt;code&gt;NSPredicate&lt;/code&gt; language is supported, if something is not supported you’ll get an exception&lt;/p&gt;
&lt;p&gt;Predicates such as “equal”, “greater than”, “distance to location”, string tokenizing, and OR / AND are supported&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;[publicDatabase performQuery:query inZoneWithID:nil completionHandler: { … }];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Subscriptions:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you repeatedly run the same query, polling for the same data, you can ask the server to run the query for you and notify you immediately when a new record is added&lt;/p&gt;
&lt;p&gt;You do that by creating a subscription (&lt;code&gt;CKSubscription&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;A subscription includes: &lt;code&gt;RecordType&lt;/code&gt;, &lt;code&gt;NSPredicate&lt;/code&gt; and push configuration (&lt;code&gt;CKNotificationInfo&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Your app is notified of changes through a push notification with some additional data&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;CKSubscription *subscription =
  [[CKSubscription alloc] initWithRecordType:@"Party"
                                   predicate:predicate
                                     options:CKSubscriptionOptionsFiresOnRecordCreation];

[publicDatabase saveSubscription:subscription completionHandler: { … }];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Pushes are handled through the usual push API:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo;&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Build a &lt;code&gt;CKNotification&lt;/code&gt; object from the user info:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;CKNotification *cloudKitNotification =
    [CKNotification notificationFromRemoteNotificationDictionary:userInfo];

NSString *alertBody = cloudKitNotification.alertBody;

if (cloudKitNotification.notificationType == CKNotificationTypeQuery) {
    CKQueryNotification *queryNotification = cloudKitNotification;
    CKRecordID *recordID = [queryNotification recordID];
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Handling user accounts:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Your application does not get direct access to any user identifiers like iCloud email address&lt;/p&gt;
&lt;p&gt;Instead, in each container each user is represented as a unique ID within that container that doesn’t change unless the user switches to another account&lt;/p&gt;
&lt;p&gt;The same user will have a different ID in a different CloudKit container&lt;/p&gt;
&lt;p&gt;The ID is an instance of &lt;code&gt;CKRecordID&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;[[CKContainer defaultContainer] fetchUserRecordIDWithCompletionHandler: { … }];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Each user has a user record representing them (which is almost like any other record) with their user id and record type = &lt;code&gt;CKRecordTypeUserRecord&lt;/code&gt;, one in the private database, and another with the same ID in the public database&lt;/p&gt;
&lt;p&gt;You can set and read any key-value data on this record like on other records&lt;/p&gt;
&lt;p&gt;However, these records aren’t created by you and can’t be queried to get a list of users&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;[publicDatabase fetchRecordWithID:userRecordID completionHandler: { … }];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;User discovery:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can ask the user to allow you to make them discoverable by other users (they get a request popup)&lt;/p&gt;
&lt;p&gt;If they agree, they can be looked up by user ID, specific email, or by fetching a list of all users matching your user’s contacts from the address book (this doesn’t give your app access to the address book itself, just a list of matching users)&lt;/p&gt;
&lt;p&gt;You get back record IDs, first &amp;amp; last names of users, but no emails&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;[defaultContainer discoverAllContactUserInfosWithCompletionHandler: { … }];&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Returns an array of &lt;code&gt;CKDiscoveredUserInfo&lt;/code&gt; objects with properties &lt;code&gt;userRecordID&lt;/code&gt;, &lt;code&gt;firstName&lt;/code&gt;, &lt;code&gt;lastName&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;[note: this API has been replaced since then with a new one that returns &lt;code&gt;CKUserIdentity&lt;/code&gt; objects]&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;When to use CloudKit vs. other APIs?&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;CloudKit doesn’t replace or deprecate any existing iCloud APIs [yet ;P], it’s just an additional tool&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Key-value store:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;asynchronous, small amounts of data&lt;/li&gt;
&lt;li&gt;mostly for application preferences&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;iCloud Drive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;works on files and folders&lt;/li&gt;
&lt;li&gt;on OSX it makes a full offline cache of the drive&lt;/li&gt;
&lt;li&gt;good for document-centric apps&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;iCloud Core Data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;built on top of iCloud Drive&lt;/li&gt;
&lt;li&gt;good for keeping private, structured data (custom databases) in sync&lt;/li&gt;
&lt;li&gt;note: the whole data set is downloaded to each device&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;CloudKit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;good for sharing public data between users, both structured data and large files&lt;/li&gt;
&lt;li&gt;good for large data sets where not every device needs to have a copy of the whole database&lt;/li&gt;
&lt;li&gt;for attaching some data to the user’s identity and sharing info between users that know each other&lt;/li&gt;
&lt;li&gt;more low-level, your app is in control of when any information is downloaded or uploaded to the iCloud servers, and has responsibility for handling sync&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc19/swiftui-on-watchos/</id>
    <title>SwiftUI on watchOS</title>
    <published>2020-09-08T13:56:24+02:00</published>
    <updated>2020-09-08T13:56:24+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc19/swiftui-on-watchos/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;SwiftUI offers apps some capabilities that were not possible before, e.g. swipe to delete and reordering in lists, or an easy way to do custom graphics and animations&lt;/p&gt;
&lt;p&gt;Fully integrated with WatchKit, both ways&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;SwiftUI allows you to use the same code on all platforms&lt;/p&gt;
&lt;p&gt;However, Apple Watch is a very special platform; a watchOS app should not be just a tiny version of the iOS app, it should be specifically designed for the Watch by only picking the right elements of the experience&lt;/p&gt;
&lt;p&gt;Building an Apple Watch app is building the whole experience, not just the main app, but also complications, notifications, Siri interface&amp;nbsp;– depending on the app&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use subclasses of &lt;code&gt;WKHostingController&lt;/code&gt; to embed SwiftUI views in an interface controller that WatchKit can use&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;.font(.system(.headline, design: .rounded)&lt;/code&gt;)&amp;nbsp;– uses a rounded version of San Francisco&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.listRowPlatterColor(topic.color)&lt;/code&gt;&amp;nbsp;– sets the color of the list cell background&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.listStyle(.carousel)&lt;/code&gt;&amp;nbsp;– a list design which centers the currently focused item on the screen&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.onMove { }&lt;/code&gt;&amp;nbsp;– enables drag to reorder&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.onDelete { }&lt;/code&gt;&amp;nbsp;– enables swipe to delete&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;For notification UI, inherit from &lt;code&gt;WKUserNotificationHostingController&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When a notification is received (&lt;code&gt;didReceive(_:)&lt;/code&gt;), the view body is automatically invalidated and reloaded&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;strong&gt;Using digital crown:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Fluent scrolling between the beginning and the end:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.digitalCrownRotation($binding, from:, through:)&lt;/code&gt;&lt;/p&gt;
&lt;p class="arrow"&gt;→ lets you create some kind of custom scrollable container&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Discrete values:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.digitalCrownRotation($binding, from:, through:, by:)&lt;/code&gt;&lt;/p&gt;
&lt;p class="arrow"&gt;→ for building interfaces where e.g. some value moves up or down by 1 when scrolling&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Going around in a circle:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.digitalCrownRotation($binding, from:, through:, by:, sensitivity:, isContinuous: true)&lt;/code&gt;&lt;/p&gt;
&lt;p class="arrow"&gt;→ sensitivity says how fast it rotates&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;.focusable(true)&lt;/code&gt;&amp;nbsp;– lets the user switch focus between elements; digital crown events go to the focused item&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc20/explore-logging-in-swift/</id>
    <title>Explore logging in Swift</title>
    <published>2020-09-07T18:31:28+02:00</published>
    <updated>2020-09-07T18:31:28+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc20/explore-logging-in-swift/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;Xcode 12 introduces new APIs for the unified logging system:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;import os

let logger = Logger(subsystem: "com.example.Fruta", category: "giftcards")
logger.log("Started a task")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;It's now much easier to pass arguments to logs using new Swift interpolation than the old &lt;code&gt;os_log&lt;/code&gt; API with C-style formatters:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;logger.log("Started a task \(taskId)")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Like in &lt;code&gt;os_log&lt;/code&gt;, it does not generate the resulting formatted string at the moment of logging, but stores an optimized representation and defers formatting to a later moment&lt;/p&gt;
&lt;p&gt;You can pass any values like numeric types, ObjC types that implement &lt;code&gt;description&lt;/code&gt; and any type conforming to &lt;code&gt;CustomStringConvertible&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Non-numeric data is private by default:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;logger.log("Paid with bank account \(accountNr)")
// -&amp;gt; Paid with bank account &amp;lt;private&amp;gt;&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To actually display the values, use the &lt;code&gt;privacy:&lt;/code&gt; parameter:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;logger.log("Ordered smoothie \(smoothieName, privacy: .public)")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also log private data in an obfuscated form, but hashed in such a way that each value gets its unique hash which you can track through a chain of logs:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;logger.log("Paid with bank account: \(accountNumber, privacy: .private(mask: .hash))")
// -&amp;gt; Paid with bank account &amp;lt;mask.hash:'CSvWylJ63...'&amp;gt;&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Collecting recent logs from a connected device to a file:&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;log collect --device --start "2020-06-22 9:41" --output fruta.logarchive&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Log levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;debug&lt;/code&gt;&amp;nbsp;– useful only during debugging&lt;/li&gt;
&lt;li&gt;&lt;code&gt;info&lt;/code&gt;&amp;nbsp;– helpful but not essential for troubleshooting&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notice&lt;/code&gt; (default)&amp;nbsp;– essential for troubleshooting&lt;/li&gt;
&lt;li&gt;&lt;code&gt;error&lt;/code&gt;&amp;nbsp;– expected errors&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fault&lt;/code&gt;&amp;nbsp;– unexpected errors, assumptions that weren’t true, potential bugs&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The Logger has a separate method for each log level:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;logger.debug(…)
logger.error(…)
// etc.&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Take into account that not all logs are persisted after the app finishes execution, some can only be live streamed&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;&lt;code&gt;debug&lt;/code&gt; &amp;nbsp;⭢&amp;nbsp; not persisted by default&lt;/p&gt;
&lt;p&gt;&lt;code&gt;info&lt;/code&gt; &amp;nbsp;⭢&amp;nbsp; saved to memory, only persisted if collected using &lt;code&gt;log collect&lt;/code&gt; before they disappear&lt;/p&gt;
&lt;p&gt;&lt;code&gt;notice&lt;/code&gt; &amp;nbsp;⭢&amp;nbsp; persisted up to a storage limit, older messages are purged after some time&lt;/p&gt;
&lt;p&gt;&lt;code&gt;error&lt;/code&gt;, &lt;code&gt;fault&lt;/code&gt; &amp;nbsp;⭢&amp;nbsp; persisted for a longer time than notice messages (typically a few days)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;The level also affects logging performance: debug logs are the fastest, error and fault are the slowest&lt;/p&gt;
&lt;p&gt;It’s safe to call even some slower code within log messages that will not be logged&amp;nbsp;– the code isn’t run unless the log is enabled:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;logger.debug("\(slowFunction(data))")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Logger provides a number of built-in formatters that let you customize how an interpolated value is displayed:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;logger.log("\(cardId, align: .left(columns: 5))")
logger.log("\(seconds, format: .fixed(precision: 2))")
logger.log("\(data, format: .hex, align: .right(columns: 4))")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;+ others like decimal, exponential, octal&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Existing &lt;code&gt;os_log(…)&lt;/code&gt; function also accepts the new interpolating parameters (when running on latest OS versions)&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <id>https://mackuba.eu/notes/wwdc18/measuring-performance-using-logging/</id>
    <title>Measuring Performance Using Logging</title>
    <published>2020-09-07T18:01:44+02:00</published>
    <updated>2020-09-07T18:01:44+02:00</updated>
    <link href="https://mackuba.eu/notes/wwdc18/measuring-performance-using-logging/"/>
    <content type="html">&lt;div class="block"&gt;
&lt;p&gt;Signposts&amp;nbsp;– a new feature of the &lt;code&gt;os_log&lt;/code&gt; API&lt;/p&gt;
&lt;p&gt;Useful for debugging performance issues&lt;/p&gt;
&lt;p&gt;Integrated with Instruments, which can visualize activity over time using signposts&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Signposts allow you to mark the beginning and end of a piece of work and mark it with some kind of label&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;import os.signpost

let refreshLog = OSLog(subsystem: "…", category: "…")

os_signpost(.begin, log: refreshLog, name: "Fetch Asset")
// …do actual work…
os_signpost(.end, log: refreshLog, name: "Fetch Asset")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Ranges of signposts with different names can overlap&amp;nbsp;– e.g. you can have one signpost covering the whole process and smaller signposts covering specific single tasks that it consists of:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;os_signpost(.begin, log: log, name: "Load Data")

os_signpost(.begin, log: log, name: "Fetch Asset")
// …
os_signpost(.end, log: log, name: "Fetch Asset")

os_signpost(.begin, log: log, name: "Parse JSON")
// …
os_signpost(.end, log: log, name: "Parse JSON")

os_signpost(.end, log: log, name: "Load Data")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you run multiple tasks of the same kind, to let the system differentiate between them and know which begin matches which end, you can add a “signpost ID”:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;os_signpost(.begin, log: log, name: "Load Data")

for asset in assets {
    let spid = OSSignpostID(log: log)

    os_signpost(.begin, log: refreshLog, name: "Fetch Asset", signpostID: spid)
    // …do actual work…
    os_signpost(.end, log: refreshLog, name: "Fetch Asset", signpostID: spid)
}

os_signpost(.end, log: log, name: "Load Data")&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can also pass your model object when generating the signpost ID&amp;nbsp;– then it will always use the same signpost ID for the same object, and you don't have to store the signpost object, just your model:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let spid = OSSignpostID(log: log, object: asset)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;There is also an option of passing an additional string argument to begin/end to provide some context (e.g. to differentiate between different possible ways to finish an activity, like success/failure)&lt;/p&gt;
&lt;p&gt;The string also accepts format arguments like &lt;code&gt;os_log&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;os_signpost(.begin, log: log, name: "Compute Physics",
    "Calculating %{public}s: %d %d %d %d", description, x1, y2, x2, y2)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Apart from marking the beginning and end, you can also mark specific points in time during the process using the &lt;code&gt;.event&lt;/code&gt; signpost type:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;os_signpost(.event, log: log, name: "Fetch Asset",
    "Received chunk of data, size %d", size)&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Signposts are very optimized internally, they’re built to minimize the time spent when logging, same as the whole &lt;code&gt;os_log&lt;/code&gt; API&lt;/p&gt;
&lt;p&gt;This means you can emit a lot of signposts even in a very tight window, when investigating a performance bottleneck&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;If you still really want to enable or disable some signpost logs based on some conditions, you can swap your logger object with &lt;code&gt;OSLog.disabled&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;let refreshLog: OSLog

if ProcessInfo.processInfo.environment.keys.contains("SIGNPOSTS_REFRESH") {
    refreshLog = OSLog(…)
} else {
    refreshLog = .disabled
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;To conditionally disable some expensive code that is only useful for debugging, you can check the &lt;code&gt;signpostsEnabled&lt;/code&gt; property:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: swift"&gt;if refreshLog.signpostsEnabled {
    let information = collectInfo()
    os_signpost(…, information)
}&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;All APIs are also available in C &amp;amp; ObjC:&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="brush: objc"&gt;#include &amp;lt;os/signpost.h&amp;gt;

os_signpost_interval_begin()
os_signpost_interval_end()
os_signpost_event_emit()
os_signpost_id_t
OS_LOG_DISABLED&lt;/pre&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use the formatter &lt;code&gt;%{xcode:size-in-bytes}u&lt;/code&gt; to let Xcode &amp;amp; Instruments know that the logged value is a byte size of some data&lt;/p&gt;
&lt;p class="arrow"&gt;→ This is one of so called “Engineering types”&amp;nbsp;– find more in the Instruments Help menu, in the Instruments developer guide&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Instruments:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Use the “os_signpost” instrument to profile using signposts&lt;/p&gt;
&lt;p&gt;After recording some data, you can see the signpost names in the sidebar on the left and signpost ranges with the optional begin/end comments marked on the chart&lt;/p&gt;
&lt;p&gt;In the bottom pane you can see count and duration statistics, grouped by category, signpost, id and comment&lt;/p&gt;
&lt;p&gt;Clicking the arrow button next to a specific message row shows you a list of all instances of this specific message (selecting them highlights them on the timeline)&lt;/p&gt;
&lt;p&gt;For metadata like logged byte sizes of downloads, you can choose “Summary: Metadata Statistics” to see total/min/max/avg of each type of value&lt;/p&gt;
&lt;/div&gt;
&lt;div class="block"&gt;
&lt;p&gt;Live streaming signpost logs to Instruments (“Immediate mode”) adds some overhead, so if you want to avoid that while debugging some performance-critical code, click and hold the Record button to access recording options and change mode to “Last n seconds”&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Points of interest:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;Points of interest is a special log category of OSLog&lt;/p&gt;
&lt;p&gt;It’s meant for logging important actions taken by the user, like opening some specific screen&lt;/p&gt;
&lt;p&gt;Normal &lt;code&gt;os_log&lt;/code&gt; logs and signpost logs logged to this category appear in a special separate Instruments timeline, which lets you visualize what was happening in the app at the moment when something happened on other charts like CPU usage&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Custom Instruments packages:&lt;/h3&gt;
&lt;div class="block"&gt;
&lt;p&gt;You can now build your custom Instruments packages, defined as an XML file in a separate target, which appear as a new kind of template when starting Instruments&lt;/p&gt;
&lt;p&gt;This lets you process and present collected signpost data in a different way that makes sense for the specific problem you’re analyzing&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
</feed>
