View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vidéos

Ouvrir le menu Fermer le menu
  • Collections
  • Toutes les vidéos
  • À propos

Plus de vidéos

  • À propos
  • Code
  • What's new in SwiftUI

    There's never been a better time to develop your apps with SwiftUI. Discover the latest updates to the UI framework — including lists, buttons, and text fields — and learn how these features can help you more fully adopt SwiftUI in your app. Find out how to create beautiful, visually-rich graphics using the canvas view, materials, and enhancements to symbols. Explore multi-column tables on macOS, refinements to focus and keyboard interaction, and the multi-platform search API. And we'll show you how to take advantage of features like Swift concurrency, a brand new AttributedString, format styles, localization, and so much more.

    Ressources

    • SwiftUI
    • Human Interface Guidelines: Designing for iOS
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC21

    • Add rich graphics to your SwiftUI app
    • Bring Core Data concurrency to Swift and SwiftUI
    • Build a workout app for Apple Watch
    • Craft search experiences in SwiftUI
    • Demystify SwiftUI
    • Direct and reflect focus in SwiftUI
    • Discover concurrency in SwiftUI
    • Localize your SwiftUI app
    • Meet Shortcuts for macOS
    • Principles of great widgets
    • SF Symbols in SwiftUI
    • Swift concurrency: Update a sample app
    • SwiftUI Accessibility: Beyond the basics
    • What's new in Foundation
    • What's new in watchOS 8
    • What‘s new in Swift
    • What’s new in SF Symbols
  • Rechercher dans cette vidéo…
    • 3:29 - AsyncImage

      struct ContentView: View {
        @StateObject private var photoStore = PhotoStore()
      
        var body: some View {
          NavigationView {
            ScrollView {
              LazyVGrid(columns: [GridItem(.adaptive(minimum: 420))]) {
                ForEach(photoStore.photos) { photo in
                  AsyncImage(url: photo.url)
                    .frame(width: 400, height: 266)
                    .mask(RoundedRectangle(cornerRadius: 16))
                }
              }
              .padding()
            }
            .navigationTitle("Superhero Recruits")
          }
          .navigationViewStyle(.stack)
        }
      }
      
      class PhotoStore: ObservableObject {
        @Published var photos: [Photo] = [/* Default photos */]
      }
      
      struct Photo: Identifiable {
        var id: URL { url }
        var url: URL
      }
    • 3:45 - AsyncImage with custom placeholder

      struct ContentView: View {
        @StateObject private var photoStore = PhotoStore()
      
        var body: some View {
          NavigationView {
            ScrollView {
              LazyVGrid(columns: [GridItem(.adaptive(minimum: 420))]) {
                ForEach(photoStore.photos) { photo in
                  AsyncImage(url: photo.url) { image in
                    image
                      .resizable()
                      .aspectRatio(contentMode: .fill)
                  } placeholder: {
                    randomPlaceholderColor()
                      .opacity(0.2)
                  }
                  .frame(width: 400, height: 266)
                  .mask(RoundedRectangle(cornerRadius: 16))
                }
              }
              .padding()
            }
            .navigationTitle("Superhero Recruits")
          }
          .navigationViewStyle(.stack)
        }
      }
      
      class PhotoStore: ObservableObject {
        @Published var photos: [Photo] = [/* Default photos */]
      }
      
      struct Photo: Identifiable {
        var id: URL { url }
        var url: URL
      }
      
      func randomPlaceholderColor() -> Color {
        placeholderColors.randomElement()!
      }
      
      let placeholderColors: [Color] = [
        .red, .blue, .orange, .mint, .purple, .yellow, .green, .pink
      ]
    • 4:00 - AsyncImage with custom animations and error handling

      struct Contentiew: View {
        @StateObject private var photoStore = PhotoStore()
      
        var body: some View {
          NavigationView {
            ScrollView {
              LazyVGrid(columns: [GridItem(.adaptive(minimum: 420))]) {
                ForEach(photoStore.photos) { photo in
                  AsyncImage(url: photo.url, transaction: .init(animation: .spring())) { phase in
                    switch phase {
                    case .empty:
                      randomPlaceholderColor()
                        .opacity(0.2)
                        .transition(.opacity.combined(with: .scale))
                    case .success(let image):
                      image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .transition(.opacity.combined(with: .scale))
                    case .failure(let error):
                      ErrorView(error)
                    @unknown default:
                      ErrorView()
                    }
                  }
                  .frame(width: 400, height: 266)
                  .mask(RoundedRectangle(cornerRadius: 16))
                }
              }
              .padding()
            }
            .navigationTitle("Superhero Recruits")
          }
          .navigationViewStyle(.stack)
        }
      }
      
      struct ErrorView: View {
        var error: Error?
      
        init(_ error: Error? = nil) {
          self.error = error
        }
      
        var body: some View {
          Text("Error") // Display the error
        }
      }
      
      class PhotoStore: ObservableObject {
        @Published var photos: [Photo] = [/* Default photos */]
      }
      
      struct Photo: Identifiable {
        var id: URL { url }
        var url: URL
      }
      
      func randomPlaceholderColor() -> Color {
        placeholderColors.randomElement()!
      }
      
      let placeholderColors: [Color] = [
        .red, .blue, .orange, .mint, .purple, .yellow, .green, .pink
      ]
    • 4:24 - refreshable() modifier

      struct ContentView: View {
        @StateObject private var photoStore = PhotoStore()
      
        var body: some View {
          NavigationView {
            List {
              ForEach(photoStore.photos) { photo in
                AsyncImage(url: photo.url)
                  .frame(minHeight: 200)
                  .mask(RoundedRectangle(cornerRadius: 16))
                  .listRowSeparator(.hidden)
              }
            }
            .listStyle(.plain)
            .navigationTitle("Superhero Recruits")
            .refreshable {
              await photoStore.update()
            }
          }
        }
      }
      
      class PhotoStore: ObservableObject {
        @Published var photos: [Photo] = [/* Default photos */]
      
        func update() async {
          // Fetch new photos
        }
      }
      
      struct Photo: Identifiable {
        var id: URL { url }
        var url: URL
      }
    • 4:58 - task() modifier

      struct ContentView: View {
        @StateObject private var photoStore = PhotoStore()
      
        var body: some View {
          NavigationView {
            List {
              ForEach(photoStore.photos) { photo in
                AsyncImage(url: photo.url)
                  .frame(minHeight: 200)
                  .mask(RoundedRectangle(cornerRadius: 16))
                  .listRowSeparator(.hidden)
              }
            }
            .listStyle(.plain)
            .navigationTitle("Superhero Recruits")
            .refreshable {
              await photoStore.update()
            }
            .task {
              await photoStore.update()
            }
          }
        }
      }
      
      class PhotoStore: ObservableObject {
        @Published var photos: [Photo] = [/* Default photos */]
      
        func update() async {
          // Fetch new photos
        }
      }
      
      struct Photo: Identifiable {
        var id: URL { url }
        var url: URL
      }
    • 5:28 - task() modifier iterating over an AsyncSequence

      struct ContentView: View {
        @StateObject private var photoStore = PhotoStore()
      
        var body: some View {
          NavigationView {
            List {
              ForEach(photoStore.photos) { photo in
                AsyncImage(url: photo.url)
                  .frame(minHeight: 200)
                  .mask(RoundedRectangle(cornerRadius: 16))
                  .listRowSeparator(.hidden)
              }
            }
            .listStyle(.plain)
            .navigationTitle("Superhero Recruits")
            .refreshable {
              await photoStore.update()
            }
            .task {
              for await photo in photoStore.newestPhotos {
                photoStore.push(photo)
              }
            }
          }
        }
      }
      
      class PhotoStore: ObservableObject {
        @Published var photos: [Photo] = [/* Default photos */]
      
        var newestPhotos: NewestPhotos {
          NewestPhotos()
        }
      
        func update() async {
          // Fetch new photos from remote service
        }
      
        func push(_ photo: Photo) {
          photos.append(photo)
        }
      }
      
      struct NewestPhotos: AsyncSequence {
        struct AsyncIterator: AsyncIteratorProtocol {
          func next() async -> Photo? {
            // Fetch next photo from remote service
          }
        }
      
        func makeAsyncIterator() -> AsyncIterator {
          AsyncIterator()
        }
      }
      
      struct Photo: Identifiable {
        var id: URL { url }
        var url: URL
      }
    • 7:02 - Non-interactive directions list

      struct ContentView: View {
        @State var directions: [Direction] = [
          Direction(symbol: "car", color: .mint, text: "Drive to SFO"),
          Direction(symbol: "airplane", color: .blue, text: "Fly to SJC"),
          Direction(symbol: "tram", color: .purple, text: "Ride to Cupertino"),
          Direction(symbol: "bicycle", color: .orange, text: "Bike to Apple Park"),
          Direction(symbol: "figure.walk", color: .green, text: "Walk to pond"),
          Direction(symbol: "lifepreserver", color: .blue, text: "Swim to the center"),
          Direction(symbol: "drop", color: .indigo, text: "Dive to secret airlock"),
          Direction(symbol: "tram.tunnel.fill", color: .brown, text: "Ride through underground tunnels"),
          Direction(symbol: "key", color: .red, text: "Enter door code:"),
        ]
      
        var body: some View {
          NavigationView {
            List(directions) { direction in
              Label {
                Text(direction.text)
              } icon: {
                DirectionsIcon(direction)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Secret Hideout")
          }
        }
      }
      
      struct Direction: Identifiable {
        var id = UUID()
        var symbol: String
        var color: Color
        var text: String
      }
      
      private struct DirectionsIcon: View {
        var direction: Direction
      
        init(_ direction: Direction) {
          self.direction = direction
        }
      
        var body: some View {
          Image(systemName: direction.symbol)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .foregroundStyle(.white)
            .padding(6)
            .frame(width: 33, height: 33)
            .background(direction.color, in: RoundedRectangle(cornerRadius: 8))
        }
      }
    • 8:08 - Interactive directions list

      struct ContentView: View {
        @State var directions: [Direction] = [
          Direction(symbol: "car", color: .mint, text: "Drive to SFO"),
          Direction(symbol: "airplane", color: .blue, text: "Fly to SJC"),
          Direction(symbol: "tram", color: .purple, text: "Ride to Cupertino"),
          Direction(symbol: "bicycle", color: .orange, text: "Bike to Apple Park"),
          Direction(symbol: "figure.walk", color: .green, text: "Walk to pond"),
          Direction(symbol: "lifepreserver", color: .blue, text: "Swim to the center"),
          Direction(symbol: "drop", color: .indigo, text: "Dive to secret airlock"),
          Direction(symbol: "tram.tunnel.fill", color: .brown, text: "Ride through underground tunnels"),
          Direction(symbol: "key", color: .red, text: "Enter door code:"),
        ]
      
        var body: some View {
          NavigationView {
            List($directions) { $direction in
              Label {
                TextField("Instructions", text: $direction.text)
              } icon: {
                DirectionsIcon(direction)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Secret Hideout")
          }
        }
      }
      
      struct Direction: Identifiable {
        var id = UUID()
        var symbol: String
        var color: Color
        var text: String
      }
      
      private struct DirectionsIcon: View {
        var direction: Direction
      
        init(_ direction: Direction) {
          self.direction = direction
        }
      
        var body: some View {
          Image(systemName: direction.symbol)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .foregroundStyle(.white)
            .padding(6)
            .frame(width: 33, height: 33)
            .background(direction.color, in: RoundedRectangle(cornerRadius: 8))
        }
      }
    • 8:49 - Interactive directions list using ForEach

      struct ContentView: View {
        @State var directions: [Direction] = [
          Direction(symbol: "car", color: .mint, text: "Drive to SFO"),
          Direction(symbol: "airplane", color: .blue, text: "Fly to SJC"),
          Direction(symbol: "tram", color: .purple, text: "Ride to Cupertino"),
          Direction(symbol: "bicycle", color: .orange, text: "Bike to Apple Park"),
          Direction(symbol: "figure.walk", color: .green, text: "Walk to pond"),
          Direction(symbol: "lifepreserver", color: .blue, text: "Swim to the center"),
          Direction(symbol: "drop", color: .indigo, text: "Dive to secret airlock"),
          Direction(symbol: "tram.tunnel.fill", color: .brown, text: "Ride through underground tunnels"),
          Direction(symbol: "key", color: .red, text: "Enter door code:"),
        ]
      
        var body: some View {
          NavigationView {
            List {
              ForEach($directions) { $direction in
                Label {
                  TextField("Instructions", text: $direction.text)
                } icon: {
                  DirectionsIcon(direction)
                }
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Secret Hideout")
          }
        }
      }
      
      struct Direction: Identifiable {
        var id = UUID()
        var symbol: String
        var color: Color
        var text: String
      }
      
      private struct DirectionsIcon: View {
        var direction: Direction
      
        init(_ direction: Direction) {
          self.direction = direction
        }
      
        var body: some View {
          Image(systemName: direction.symbol)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .foregroundStyle(.white)
            .padding(6)
            .frame(width: 33, height: 33)
            .background(direction.color, in: RoundedRectangle(cornerRadius: 8))
        }
      }
    • 9:09 - listRowSeparatorTint() modifier

      struct ContentView: View {
        @State var directions: [Direction] = [
          Direction(symbol: "car", color: .mint, text: "Drive to SFO"),
          Direction(symbol: "airplane", color: .blue, text: "Fly to SJC"),
          Direction(symbol: "tram", color: .purple, text: "Ride to Cupertino"),
          Direction(symbol: "bicycle", color: .orange, text: "Bike to Apple Park"),
          Direction(symbol: "figure.walk", color: .green, text: "Walk to pond"),
          Direction(symbol: "lifepreserver", color: .blue, text: "Swim to the center"),
          Direction(symbol: "drop", color: .indigo, text: "Dive to secret airlock"),
          Direction(symbol: "tram.tunnel.fill", color: .brown, text: "Ride through underground tunnels"),
          Direction(symbol: "key", color: .red, text: "Enter door code:"),
        ]
      
        var body: some View {
          NavigationView {
            List {
              ForEach($directions) { $direction in
                Label {
                  TextField("Instructions", text: $direction.text)
                } icon: {
                  DirectionsIcon(direction)
                }
                .listRowSeparatorTint(direction.color)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Secret Hideout")
          }
        }
      }
      
      struct Direction: Identifiable {
        var id = UUID()
        var symbol: String
        var color: Color
        var text: String
      }
      
      private struct DirectionsIcon: View {
        var direction: Direction
      
        init(_ direction: Direction) {
          self.direction = direction
        }
      
        var body: some View {
          Image(systemName: direction.symbol)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .foregroundStyle(.white)
            .padding(6)
            .frame(width: 33, height: 33)
            .background(direction.color, in: RoundedRectangle(cornerRadius: 8))
        }
      }
    • 9:38 - listRowSeparator() modifier

      struct ContentView: View {
        @State var directions: [Direction] = [
          Direction(symbol: "car", color: .mint, text: "Drive to SFO"),
          Direction(symbol: "airplane", color: .blue, text: "Fly to SJC"),
          Direction(symbol: "tram", color: .purple, text: "Ride to Cupertino"),
          Direction(symbol: "bicycle", color: .orange, text: "Bike to Apple Park"),
          Direction(symbol: "figure.walk", color: .green, text: "Walk to pond"),
          Direction(symbol: "lifepreserver", color: .blue, text: "Swim to the center"),
          Direction(symbol: "drop", color: .indigo, text: "Dive to secret airlock"),
          Direction(symbol: "tram.tunnel.fill", color: .brown, text: "Ride through underground tunnels"),
          Direction(symbol: "key", color: .red, text: "Enter door code:"),
        ]
      
        var body: some View {
          NavigationView {
            List {
              ForEach($directions) { $direction in
                Label {
                  TextField("Instructions", text: $direction.text)
                } icon: {
                  DirectionsIcon(direction)
                }
                .listRowSeparator(.hidden)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Secret Hideout")
          }
        }
      }
      
      struct Direction: Identifiable {
        var id = UUID()
        var symbol: String
        var color: Color
        var text: String
      }
      
      private struct DirectionsIcon: View {
        var direction: Direction
      
        init(_ direction: Direction) {
          self.direction = direction
        }
      
        var body: some View {
          Image(systemName: direction.symbol)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .foregroundStyle(.white)
            .padding(6)
            .frame(width: 33, height: 33)
            .background(direction.color, in: RoundedRectangle(cornerRadius: 8))
        }
      }
    • 10:08 - Swipe actions

      struct ContentView: View {
        @State private var characters = CharacterStore(StoryCharacter.previewData)
      
        var body: some View {
          NavigationView {
            List {
              if !characters.pinned.isEmpty {
                Section("Pinned") {
                  sectionContent(for: $characters.pinned)
                }
              }
              Section("Heroes & Villains") {
                sectionContent(for: $characters.unpinned)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Characters")
          }
        }
      
        @ViewBuilder
        private func sectionContent(for characters: Binding<[StoryCharacter]>) -> some View {
          ForEach(characters) { $character in
            CharacterProfile(character)
              .swipeActions {
                Button {
                  togglePinned(for: $character)
                } label: {
                  if character.isPinned {
                    Label("Unpin", systemImage: "pin.slash")
                  } else {
                    Label("Pin", systemImage: "pin")
                  }
                }
                .tint(.yellow)
              }
          }
        }
      
        private func togglePinned(for character: Binding<StoryCharacter>) {
          withAnimation {
            var tmp = character.wrappedValue
            tmp.isPinned.toggle()
            tmp.lastModified = Date()
            character.wrappedValue = tmp
          }
        }
      
        private func delete<C: RangeReplaceableCollection & MutableCollection>(
          _ character: StoryCharacter, in characters: Binding<C>
        ) where C.Element == StoryCharacter {
          withAnimation {
            if let i = characters.wrappedValue.firstIndex(where: {
              $0.id == character.id
            }) {
              characters.wrappedValue.remove(at: i)
            }
          }
        }
      }
      
      struct CharacterProfile: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          NavigationLink {
            Text(character.name)
          } label: {
            HStack {
              HStack {
                let symbol = Image(systemName: character.symbol)
                  .resizable()
                  .aspectRatio(contentMode: .fit)
                  .foregroundStyle(.white)
                  .padding(6)
                  .frame(width: 33, height: 33)
      
                if character.isVillain {
                  symbol
                    .background(character.color, in: RoundedRectangle(cornerRadius: 8))
                } else {
                  symbol
                    .background(character.color, in: Circle())
                }
              }
              VStack(alignment: .leading, spacing: 2) {
                HStack(alignment: .center) {
                  Text(character.name)
                    .bold()
                    .foregroundStyle(.primary)
                }
                HStack(spacing: 4) {
                  Text(character.isVillain ? "VILLAIN" : "HERO")
                    .bold()
                    .font(.caption2.weight(.heavy))
                    .foregroundStyle(.white)
                    .padding(.vertical, 1)
                    .padding(.horizontal, 3)
                    .background(.quaternary, in: RoundedRectangle(cornerRadius: 3))
      
                  Text(character.powers)
                    .font(.footnote)
                    .foregroundStyle(.secondary)
                }
              }
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 10:27 - Swipe actions on the leading edge

      struct ContentView: View {
        @State private var characters = CharacterStore(StoryCharacter.previewData)
      
        var body: some View {
          NavigationView {
            List {
              if !characters.pinned.isEmpty {
                Section("Pinned") {
                  sectionContent(for: $characters.pinned)
                }
              }
              Section("Heroes & Villains") {
                sectionContent(for: $characters.unpinned)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Characters")
          }
        }
      
        @ViewBuilder
        private func sectionContent(for characters: Binding<[StoryCharacter]>) -> some View {
          ForEach(characters) { $character in
            CharacterProfile(character)
              .swipeActions(edge: .leading) {
                Button {
                  togglePinned(for: $character)
                } label: {
                  if character.isPinned {
                    Label("Unpin", systemImage: "pin.slash")
                  } else {
                    Label("Pin", systemImage: "pin")
                  }
                }
                .tint(.yellow)
              }
          }
        }
      
        private func togglePinned(for character: Binding<StoryCharacter>) {
          withAnimation {
            var tmp = character.wrappedValue
            tmp.isPinned.toggle()
            tmp.lastModified = Date()
            character.wrappedValue = tmp
          }
        }
      
        private func delete<C: RangeReplaceableCollection & MutableCollection>(
          _ character: StoryCharacter, in characters: Binding<C>
        ) where C.Element == StoryCharacter {
          withAnimation {
            if let i = characters.wrappedValue.firstIndex(where: {
              $0.id == character.id
            }) {
              characters.wrappedValue.remove(at: i)
            }
          }
        }
      }
      
      struct CharacterProfile: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          NavigationLink {
            Text(character.name)
          } label: {
            HStack {
              HStack {
                let symbol = Image(systemName: character.symbol)
                  .resizable()
                  .aspectRatio(contentMode: .fit)
                  .foregroundStyle(.white)
                  .padding(6)
                  .frame(width: 33, height: 33)
      
                if character.isVillain {
                  symbol
                    .background(character.color, in: RoundedRectangle(cornerRadius: 8))
                } else {
                  symbol
                    .background(character.color, in: Circle())
                }
              }
              VStack(alignment: .leading, spacing: 2) {
                HStack(alignment: .center) {
                  Text(character.name)
                    .bold()
                    .foregroundStyle(.primary)
                }
                HStack(spacing: 4) {
                  Text(character.isVillain ? "VILLAIN" : "HERO")
                    .bold()
                    .font(.caption2.weight(.heavy))
                    .foregroundStyle(.white)
                    .padding(.vertical, 1)
                    .padding(.horizontal, 3)
                    .background(.quaternary, in: RoundedRectangle(cornerRadius: 3))
      
                  Text(character.powers)
                    .font(.footnote)
                    .foregroundStyle(.secondary)
                }
              }
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 10:32 - Swipe actions on both edges

      struct ContentView: View {
        @State private var characters = CharacterStore(StoryCharacter.previewData)
      
        var body: some View {
          NavigationView {
            List {
              if !characters.pinned.isEmpty {
                Section("Pinned") {
                  sectionContent(for: $characters.pinned)
                }
              }
              Section("Heroes & Villains") {
                sectionContent(for: $characters.unpinned)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Characters")
          }
        }
      
        @ViewBuilder
        private func sectionContent(for characters: Binding<[StoryCharacter]>) -> some View {
          ForEach(characters) { $character in
            CharacterProfile(character)
              .swipeActions(edge: .leading) {
                Button {
                  togglePinned(for: $character)
                } label: {
                  if character.isPinned {
                    Label("Unpin", systemImage: "pin.slash")
                  } else {
                    Label("Pin", systemImage: "pin")
                  }
                }
                .tint(.yellow)
              }
              .swipeActions(edge: .trailing) {
                Button(role: .destructive) {
                  delete(character, in: characters)
                } label: {
                  Label("Delete", systemImage: "trash")
                }
                Button {
                  // Open "More" menu
                } label: {
                  Label("More", systemImage: "ellipsis.circle")
                }
                .tint(Color(white: 0.8))
              }
          }
        }
      
        private func togglePinned(for character: Binding<StoryCharacter>) {
          withAnimation {
            var tmp = character.wrappedValue
            tmp.isPinned.toggle()
            tmp.lastModified = Date()
            character.wrappedValue = tmp
          }
        }
      
        private func delete<C: RangeReplaceableCollection & MutableCollection>(
          _ character: StoryCharacter, in characters: Binding<C>
        ) where C.Element == StoryCharacter {
          withAnimation {
            if let i = characters.wrappedValue.firstIndex(where: {
              $0.id == character.id
            }) {
              characters.wrappedValue.remove(at: i)
            }
          }
        }
      }
      
      struct CharacterProfile: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          NavigationLink {
            Text(character.name)
          } label: {
            HStack {
              HStack {
                let symbol = Image(systemName: character.symbol)
                  .resizable()
                  .aspectRatio(contentMode: .fit)
                  .foregroundStyle(.white)
                  .padding(6)
                  .frame(width: 33, height: 33)
      
                if character.isVillain {
                  symbol
                    .background(character.color, in: RoundedRectangle(cornerRadius: 8))
                } else {
                  symbol
                    .background(character.color, in: Circle())
                }
              }
              VStack(alignment: .leading, spacing: 2) {
                HStack(alignment: .center) {
                  Text(character.name)
                    .bold()
                    .foregroundStyle(.primary)
                }
                HStack(spacing: 4) {
                  Text(character.isVillain ? "VILLAIN" : "HERO")
                    .bold()
                    .font(.caption2.weight(.heavy))
                    .foregroundStyle(.white)
                    .padding(.vertical, 1)
                    .padding(.horizontal, 3)
                    .background(.quaternary, in: RoundedRectangle(cornerRadius: 3))
      
                  Text(character.powers)
                    .font(.footnote)
                    .foregroundStyle(.secondary)
                }
              }
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 11:14 - Basic macOS list

      struct ContentView: View {
        @State private var characters = StoryCharacter.previewData
        @State private var selection = Set<StoryCharacter.ID>()
      
        var body: some View {
          List(selection: $selection) {
            ForEach(characters) { character in
              Label {
                Text(character.name)
              } icon: {
                CharacterIcon(character)
              }
              .padding(.leading, 4)
            }
          }
          .listStyle(.inset)
          .navigationTitle("All Characters")
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(4)
              .frame(width: 20, height: 20)
      
            if character.isVillain {
              symbol
                .background(character.color, in: RoundedRectangle(cornerRadius: 4))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 11:35 - Inset list style alternating row backgrounds

      struct ContentView: View {
        @State private var characters = StoryCharacter.previewData
        @State private var selection = Set<StoryCharacter.ID>()
      
        var body: some View {
          List(selection: $selection) {
            ForEach(characters) { character in
              Label {
                Text(character.name)
              } icon: {
                CharacterIcon(character)
              }
              .padding(.leading, 4)
            }
          }
          .listStyle(.inset(alternatesRowBackgrounds: true))
          .navigationTitle("All Characters")
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(4)
              .frame(width: 20, height: 20)
      
            if character.isVillain {
              symbol
                .background(character.color, in: RoundedRectangle(cornerRadius: 4))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 12:13 - Tables

      struct ContentView: View {
        @State private var characters = StoryCharacter.previewData
      
        var body: some View {
          Table(characters) {
            TableColumn("􀟈") { CharacterIcon($0) }
              .width(20)
            TableColumn("Villain") { Text($0.isVillain ? "Villain" : "Hero") }
              .width(40)
            TableColumn("Name", value: \.name)
            TableColumn("Powers", value: \.powers)
          }
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(4)
              .frame(width: 20, height: 20)
      
            if character.isVillain {
              symbol
                .background(character.color, in: RoundedRectangle(cornerRadius: 4))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 12:49 - Tables with selection

      struct ContentView: View {
        @State private var characters = StoryCharacter.previewData
      
        // Single selection
        @State private var singleSelection: StoryCharacter.ID?
      
        // Multiple selection
        @State private var multipleSelection: Set<StoryCharacter.ID>()
      
        var body: some View {
          Table(characters, selection: $singleSelection) { // or `$multipleSelection`
            TableColumn("􀟈") { CharacterIcon($0) }
              .width(20)
            TableColumn("Villain") { Text($0.isVillain ? "Villain" : "Hero") }
              .width(40)
            TableColumn("Name", value: \.name)
            TableColumn("Powers", value: \.powers)
          }
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(4)
              .frame(width: 20, height: 20)
      
            if character.isVillain {
              symbol
                .background(character.color, in: RoundedRectangle(cornerRadius: 4))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 12:57 - Tables with selection and sorting

      struct ContentView: View {
        @State private var characters = StoryCharacter.previewData
        @State private var selection = Set<StoryCharacter.ID>()
      
        @State private var sortOrder = [KeyPathComparator(\StoryCharacter.name)]
        @State private var sorted: [StoryCharacter]?
      
        var body: some View {
          Table(sorted ?? characters, selection: $selection, sortOrder: $sortOrder) {
            TableColumn("􀟈") { CharacterIcon($0) }
              .width(20)
            TableColumn("Villain") { Text($0.isVillain ? "Villain" : "Hero") }
              .width(40)
            TableColumn("Name", value: \.name)
            TableColumn("Powers", value: \.powers)
          }
          .onChange(of: characters) { sorted = $0.sorted(using: sortOrder) }
          .onChange(of: sortOrder) { sorted = characters.sorted(using: $0) }
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(4)
              .frame(width: 20, height: 20)
      
            if character.isVillain {
              symbol
                .background(character.color, in: RoundedRectangle(cornerRadius: 4))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          get {
            all.prefix { $0.isPinned }
          }
          set {
            if let end = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(all.startIndex..<end, with: newValue)
            }
          }
        }
      
        var unpinned: [StoryCharacter] {
          get {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              return Array(all.suffix(from: start))
            } else {
              return []
            }
          }
          set {
            if let start = all.firstIndex(where: { !$0.isPinned }) {
              all.replaceSubrange(start..<all.endIndex, with: newValue)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 13:15 - CoreData Tables

      @FetchRequest(sortDescriptors: [SortDescriptor(\.name)])
      private var characters: FetchedResults<StoryCharacter>
      @State private var selection = Set<StoryCharacter.ID>()
      
      Table(characters, selection: $selection, sortOrder: $characters.sortDescriptors) {
        TableColumn("􀟈") { CharacterIcon($0) }
          .width(20)
        TableColumn("Villain") { Text($0.isVillain ? "Villain" : "Hero") }
          .width(40)
        TableColumn("Name", value: \.name)
        TableColumn("Powers", value: \.powers)
      }
    • 13:34 - Sectioned fetch requests

      @SectionedFetchRequest(
        sectionIdentifier: \.isPinned,
        sortDescriptors: [
          SortDescriptor(\.isPinned, order: .reverse),
          SortDescriptor(\.lastModified)
        ],
        animation: .default)
      private var characters: SectionedFetchResults<...>
      
      List {
        ForEach(characters) { section in
          Section(section.id ? "Pinned" : "Heroes & Villains") {
            ForEach(section) { character in
              CharacterRowView(character)
            }
          }
        }
      }
    • 15:20 - searchable() modifier

      struct ContentView: View {
        @State private var characters = CharacterStore(StoryCharacter.previewData)
      
        var body: some View {
          NavigationView {
            List {
              if characters.filterText.isEmpty {
                if !characters.pinned.isEmpty {
                  Section("Pinned") {
                    sectionContent(for: characters.pinned)
                  }
                }
                Section("Heroes & Villains") {
                  sectionContent(for: characters.unpinned)
                }
              } else {
                sectionContent(for: characters.filtered)
              }
            }
            .listStyle(.sidebar)
            .searchable(text: $characters.filterText)
            .navigationTitle("Characters")
          }
        }
      
        @ViewBuilder
        private func sectionContent(for characters: [StoryCharacter]) -> some View {
          ForEach(characters) { character in
            CharacterProfile(character)
          }
        }
      }
      
      struct CharacterProfile: View {
        var character: StoryCharacter
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          NavigationLink {
            Text(character.name)
          } label: {
            HStack {
              HStack {
                let symbol = Image(systemName: character.symbol)
                  .resizable()
                  .aspectRatio(contentMode: .fit)
                  .foregroundStyle(.white)
                  .padding(6)
                  .frame(width: 33, height: 33)
      
                if character.isVillain {
                  symbol
                    .background(character.color, in: RoundedRectangle(cornerRadius: 8))
                } else {
                  symbol
                    .background(character.color, in: Circle())
                }
              }
              VStack(alignment: .leading, spacing: 2) {
                HStack(alignment: .center) {
                  Text(character.name)
                    .bold()
                    .foregroundStyle(.primary)
                }
                HStack(spacing: 4) {
                  Text(character.isVillain ? "VILLAIN" : "HERO")
                    .bold()
                    .font(.caption2.weight(.heavy))
                    .foregroundStyle(.white)
                    .padding(.vertical, 1)
                    .padding(.horizontal, 3)
                    .background(.quaternary, in: RoundedRectangle(cornerRadius: 3))
      
                  Text(character.powers)
                    .font(.footnote)
                    .foregroundStyle(.secondary)
                }
              }
            }
          }
        }
      }
      
      struct CharacterStore {
        var all: [StoryCharacter] {
          get { _all }
          set { _all = newValue; sortAll() }
        }
        var _all: [StoryCharacter]
      
        var pinned: [StoryCharacter] {
          all.prefix { $0.isPinned }
        }
      
        var unpinned: [StoryCharacter] {
          if let start = all.firstIndex(where: { !$0.isPinned }) {
            return Array(all.suffix(from: start))
          } else {
            return []
          }
        }
      
        var filterText: String = ""
        var filtered: [StoryCharacter] {
          if filterText.isEmpty {
            return all
          } else {
            return all.filter {
              $0.name.contains(filterText) || $0.powers.contains(filterText)
            }
          }
        }
      
        init(_ characters: [StoryCharacter]) {
          _all = characters
          sortAll()
        }
      
        private mutating func sortAll() {
          _all.sort { lhs, rhs in
            if lhs.isPinned && !rhs.isPinned {
              return true
            } else if !lhs.isPinned && rhs.isPinned {
              return false
            } else {
              return lhs.lastModified < rhs.lastModified
            }
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      }
      
      extension StoryCharacter {
        static let previewData: [StoryCharacter] = [
          StoryCharacter(
            id: 0,
            name: "The View Builder",
            symbol: "hammer",
            color: .pink,
            powers: "Conjures objects on-demand.",
            isPinned: true),
          StoryCharacter(
            id: 1,
            name: "The Truth Duplicator",
            symbol: "eyes",
            color: .blue,
            powers: "Distorts reality.",
            isVillain: true),
          StoryCharacter(
            id: 2,
            name: "The Previewer",
            symbol: "viewfinder",
            color: .indigo,
            powers: "Reveals the future.",
            isPinned: true),
          StoryCharacter(
            id: 3,
            name: "The Type Eraser",
            symbol: "eye.slash",
            color: .black,
            powers: "Steals identities.",
            isVillain: true,
            isPinned: true),
          StoryCharacter(
            id: 4,
            name: "The Environment Modifier",
            symbol: "leaf",
            color: .green,
            powers: "Controls the physical world."),
          StoryCharacter(
            id: 5,
            name: "The Unstable Identifier",
            symbol: "shuffle",
            color: .brown,
            powers: "Shape-shifter, uncatchable.",
            isVillain: true),
          StoryCharacter(
            id: 6,
            name: "The Stylizer",
            symbol: "wand.and.stars.inverse",
            color: .red,
            powers: "Quartermaster of heroes."),
          StoryCharacter(
            id: 7,
            name: "The Singleton",
            symbol: "diamond",
            color: .purple,
            powers: "An evil robotic hive mind.",
            isVillain: true),
          StoryCharacter(
            id: 8,
            name: "The Geometry Reader",
            symbol: "ruler",
            color: .orange,
            powers: "Instantly scans any structure."),
          StoryCharacter(
            id: 9,
            name: "The Opaque Typist",
            symbol: "app.fill",
            color: .teal,
            powers: "Creates impenetrable disguises."),
          StoryCharacter(
            id: 10,
            name: "The Unobservable Man",
            symbol: "hand.raised.slash",
            color: .black,
            powers: "Impervious to detection.",
            isVillain: true),
        ]
      }
    • 16:22 - Drag previews

      struct ContentView: View {
        let character = StoryCharacter(
          id: 0,
          name: "The View Builder",
          symbol: "hammer",
          color: .pink,
          powers: "Conjures objects on-demand.",
          isPinned: true
        )
      
        var body: some View {
          CharacterIcon(character)
            .controlSize(.large)
            .padding()
            .onDrag {
              character.itemProvider
            } preview: {
              Label {
                Text(character.name)
              } icon: {
                CharacterIcon(character)
                  .controlSize(.small)
              }
              .padding(.vertical, 8)
              .frame(width: 150)
              .background(.white, in: RoundedRectangle(cornerRadius: 8))
            }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
      
        var itemProvider: NSItemProvider {
          let item = NSItemProvider()
          item.registerObject(name as NSString, visibility: .all)
          return item
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        #if os(iOS) || os(macOS)
        @Environment(\.controlSize) private var controlSize
        #endif
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(symbolPadding)
              .frame(width: symbolLength, height: symbolLength)
      
            if character.isVillain {
              symbol
                .background(
                  character.color, in: RoundedRectangle(cornerRadius: cornerRadius))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      
        var symbolPadding: CGFloat {
          switch controlSize {
          case .small: return 4
          case .large: return 10
          default: return 6
          }
        }
      
        var symbolLength: CGFloat {
          switch controlSize {
          case .small: return 20
          case .large: return 60
          default: return 33
          }
        }
      
        var cornerRadius: CGFloat {
          switch controlSize {
          case .small: return 4
          case .large: return 16
          default: return 8
          }
        }
      }
    • 16:48 - importsItemProviders() modifier

      import UniformTypeIdentifiers
      
      @main
      private struct Catalog: App {
        var body: some Scene {
          WindowGroup {
            ContentView()
          }
          .commands {
            ImportFromDevicesCommands()
          }
        }
      }
      
      struct ContentView: View {
        @State private var character: StoryCharacter = StoryCharacter(
          id: 0,
          name: "The View Builder",
          symbol: "hammer",
          color: .pink,
          powers: "Conjures objects on-demand.",
          isPinned: true
        )
      
        var body: some View {
          VStack {
            CharacterIcon(character)
              .controlSize(.large)
              .onDrag {
                character.itemProvider
              } preview: {
                Label {
                  Text(character.name)
                } icon: {
                  CharacterIcon(character)
                    .controlSize(.small)
                }
                .padding(.vertical, 8)
                .frame(width: 150)
                .background(.white, in: RoundedRectangle(cornerRadius: 8))
              }
      
            if let headerImage = character.headerImage {
              headerImage
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 150, height: 150)
                .mask(RoundedRectangle(cornerRadius: 16, style: .continuous))
            }
          }
          .padding()
          .importsItemProviders(StoryCharacter.headerImageTypes) { itemProviders in
            guard let first = itemProviders.first else { return false }
            async {
              character.headerImage = await StoryCharacter.loadHeaderImage(from: first)
            }
            return true
          }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
        var headerImage: Image?
      
        static var headerImageTypes: [UTType] {
          NSImage.imageTypes.compactMap { UTType($0) }
        }
      
        var itemProvider: NSItemProvider {
          let item = NSItemProvider()
          item.registerObject(name as NSString, visibility: .all)
          return item
        }
      
        static func loadHeaderImage(from itemProvider: NSItemProvider) async -> Image? {
          for type in Self.headerImageTypes.map(\.identifier) {
            if itemProvider.hasRepresentationConforming(toTypeIdentifier: type) {
              return await withCheckedContinuation { continuation in
                itemProvider.loadDataRepresentation(forTypeIdentifier: type) { data, error in
                  guard let data = data, let image = NSImage(data: data) else { return }
                  continuation.resume(returning: Image(nsImage: image))
                }
              }
            }
          }
          return nil
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        #if os(iOS) || os(macOS)
        @Environment(\.controlSize) private var controlSize
        #endif
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(symbolPadding)
              .frame(width: symbolLength, height: symbolLength)
      
            if character.isVillain {
              symbol
                .background(
                  character.color, in: RoundedRectangle(cornerRadius: cornerRadius))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      
        var symbolPadding: CGFloat {
          switch controlSize {
          case .small: return 4
          case .large: return 10
          default: return 6
          }
        }
      
        var symbolLength: CGFloat {
          switch controlSize {
          case .small: return 20
          case .large: return 60
          default: return 33
          }
        }
      
        var cornerRadius: CGFloat {
          switch controlSize {
          case .small: return 4
          case .large: return 16
          default: return 8
          }
        }
      }
    • 18:17 - exportsItemProviders() modifier

      import UniformTypeIdentifiers
      
      @main
      private struct Catalog: App {
        var body: some Scene {
          WindowGroup {
            ContentView()
          }
          .commands {
            ImportFromDevicesCommands()
          }
        }
      }
      
      struct ContentView: View {
        @State private var character: StoryCharacter = StoryCharacter(
          id: 0,
          name: "The View Builder",
          symbol: "hammer",
          color: .pink,
          powers: "Conjures objects on-demand.",
          isPinned: true
        )
      
        var body: some View {
          VStack {
            CharacterIcon(character)
              .controlSize(.large)
              .onDrag {
                character.itemProvider
              } preview: {
                Label {
                  Text(character.name)
                } icon: {
                  CharacterIcon(character)
                    .controlSize(.small)
                }
                .padding(.vertical, 8)
                .frame(width: 150)
                .background(.white, in: RoundedRectangle(cornerRadius: 8))
              }
      
            if let headerImage = character.headerImage {
              headerImage
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 150, height: 150)
                .mask(RoundedRectangle(cornerRadius: 16, style: .continuous))
            }
          }
          .padding()
          .importsItemProviders(StoryCharacter.headerImageTypes) { itemProviders in
            guard let first = itemProviders.first else { return false }
            async {
              character.headerImage = await StoryCharacter.loadHeaderImage(from: first)
            }
            return true
          }
          .exportsItemProviders(StoryCharacter.contentTypes) { [character.itemProvider] }
        }
      }
      
      struct StoryCharacter: Identifiable, Equatable {
        var id: Int64
        var name: String
        var symbol: String
        var color: Color
        var powers: String
        var isVillain: Bool = false
        var isPinned: Bool = false
        var lastModified = Date()
        var headerImage: Image?
      
        static var contentTypes: [UTType] { [.utf8PlainText] }
        static var headerImageTypes: [UTType] {
          NSImage.imageTypes.compactMap { UTType($0) }
        }
      
        var itemProvider: NSItemProvider {
          let item = NSItemProvider()
          item.registerObject(name as NSString, visibility: .all)
          return item
        }
      
        static func loadHeaderImage(from itemProvider: NSItemProvider) async -> Image? {
          for type in Self.headerImageTypes.map(\.identifier) {
            if itemProvider.hasRepresentationConforming(toTypeIdentifier: type) {
              return await withCheckedContinuation { continuation in
                itemProvider.loadDataRepresentation(forTypeIdentifier: type) { data, error in
                  guard let data = data, let image = NSImage(data: data) else { return }
                  continuation.resume(returning: Image(nsImage: image))
                }
              }
            }
          }
          return nil
        }
      }
      
      struct CharacterIcon: View {
        var character: StoryCharacter
      
        #if os(iOS) || os(macOS)
        @Environment(\.controlSize) private var controlSize
        #endif
      
        init(_ character: StoryCharacter) {
          self.character = character
        }
      
        var body: some View {
          HStack {
            let symbol = Image(systemName: character.symbol)
              .resizable()
              .aspectRatio(contentMode: .fit)
              .foregroundStyle(.white)
              .padding(symbolPadding)
              .frame(width: symbolLength, height: symbolLength)
      
            if character.isVillain {
              symbol
                .background(
                  character.color, in: RoundedRectangle(cornerRadius: cornerRadius))
            } else {
              symbol
                .background(character.color, in: Circle())
            }
          }
        }
      
        var symbolPadding: CGFloat {
          switch controlSize {
          case .small: return 4
          case .large: return 10
          default: return 6
          }
        }
      
        var symbolLength: CGFloat {
          switch controlSize {
          case .small: return 20
          case .large: return 60
          default: return 33
          }
        }
      
        var cornerRadius: CGFloat {
          switch controlSize {
          case .small: return 4
          case .large: return 16
          default: return 8
          }
        }
      }
    • 19:47 - Symbol rendering modes

      struct ContentView: View {
          var body: some View {
              VStack {
                  HStack { symbols }
                      .symbolRenderingMode(.monochrome)
                  HStack { symbols }
                      .symbolRenderingMode(.multicolor)
                  HStack { symbols }
                      .symbolRenderingMode(.hierarchical)
                  HStack { symbols }
                      .symbolRenderingMode(.palette)
                      .foregroundStyle(Color.cyan, Color.purple)
              }
              .foregroundStyle(.blue)
              .font(.title)
          }
          @ViewBuilder var symbols: some View {
              Group {
                  Image(systemName: "exclamationmark.triangle.fill")
                  Image(systemName: "pc")
                  Image(systemName: "phone.down.circle")
                  Image(systemName: "hourglass")
                  Image(systemName: "heart.fill")
                  Image(systemName: "airplane.circle.fill")
              }
              .frame(width: 40, height: 40)
          }
      }
    • 20:27 - Symbol variants

      struct ContentView: View {
          var body: some View {
              VStack {
                  HStack { symbols }
                  HStack { symbols }
                      .symbolVariant(.fill)
              }
              .foregroundStyle(.blue)
          }
          @ViewBuilder var symbols: some View {
              let heart = Image(systemName: "heart")
              Group {
                  heart
                  heart.symbolVariant(.slash)
                  heart.symbolVariant(.circle)
                  heart.symbolVariant(.square)
                  heart.symbolVariant(.rectangle)
              }
              .frame(width: 40, height: 40)
          }
      }
    • 20:42 - Tab symbol variants: iOS 13

      struct TabExample: View {
          var body: some View {
              TabView {
                  CardsView().tabItem {
                      Label("Cards", systemImage: "rectangle.portrait.on.rectangle.portrait.fill")
                  }
                  RulesView().tabItem {
                      Label("Rules", systemImage: "character.book.closed.fill")
                  }
                  ProfileView().tabItem {
                      Label("Profile", systemImage: "person.circle.fill")
                  }
                  SearchPlayersView().tabItem {
                      Label("Magic", systemImage: "sparkles")
                  }
              }
          }
      }
      
      struct CardsView: View {
          var body: some View { Color.clear }
      }
      struct RulesView: View {
          var body: some View { Color.clear }
      }
      struct ProfileView: View {
          var body: some View { Color.clear }
      }
      struct SearchPlayersView: View {
          var body: some View { Color.clear }
      }
    • 20:50 - Tab symbol variants

      @main
      struct SnippetsApp: App {
          var body: some Scene {
              WindowGroup {
                  #if os(iOS)
                  TabExample()
                  #else
                  VStack{
                      Text("Open Preferences")
                      Text("⌘,").font(.title.monospaced())
                  }
                  .fixedSize()
                  .scenePadding()
                  #endif
              }
      
              #if os(macOS)
              Settings {
                  TabExample()
              }
              #endif
          }
      }
      
      
      struct TabExample: View {
          var body: some View {
              TabView {
                  CardsView().tabItem {
                      Label("Cards", systemImage: "rectangle.portrait.on.rectangle.portrait")
                  }
                  RulesView().tabItem {
                      Label("Rules", systemImage: "character.book.closed")
                  }
                  ProfileView().tabItem {
                      Label("Profile", systemImage: "person.circle")
                  }
                  SearchPlayersView().tabItem {
                      Label("Magic", systemImage: "sparkles")
                  }
              }
          }
      }
      
      struct CardsView: View {
          var body: some View { Color.clear }
      }
      struct RulesView: View {
          var body: some View { Color.clear }
      }
      struct ProfileView: View {
          var body: some View { Color.clear }
      }
      struct SearchPlayersView: View {
          var body: some View { Color.clear }
      }
    • 21:31 - Canvas

      struct ContentView: View {
          let symbols = Array(repeating: Symbol("swift"), count: 3166)
      
          var body: some View {
              Canvas { context, size in
                  let metrics = gridMetrics(in: size)
                  for (index, symbol) in symbols.enumerated() {
                      let rect = metrics[index]
                      let image = context.resolve(symbol.image)
                      context.draw(image, in: rect.fit(image.size))
                  }
              }
          }
      
          func gridMetrics(in size: CGSize) -> SymbolGridMetrics {
              SymbolGridMetrics(size: size, numberOfSymbols: symbols.count)
          }
      }
      
      struct Symbol: Identifiable {
          let name: String
          init(_ name: String) { self.name = name }
      
          var image: Image { Image(systemName: name) }
          var id: String { name }
      }
      
      struct SymbolGridMetrics {
          let symbolWidth: CGFloat
          let symbolsPerRow: Int
          let numberOfSymbols: Int
          let insetProportion: CGFloat
      
          init(size: CGSize, numberOfSymbols: Int, insetProportion: CGFloat = 0.1) {
              let areaPerSymbol = (size.width * size.height) / CGFloat(numberOfSymbols)
              self.symbolsPerRow = Int(size.width / sqrt(areaPerSymbol))
              self.symbolWidth = size.width / CGFloat(symbolsPerRow)
              self.numberOfSymbols = numberOfSymbols
              self.insetProportion = insetProportion
          }
      
          /// Returns the frame in the grid for the symbol at `index` position.
          /// It is not valid to pass an index less than `0` or larger than the number of symbols the grid metrics was created for.
          subscript(_ index: Int) -> CGRect {
              precondition(index >= 0 && index < numberOfSymbols)
              let row = index / symbolsPerRow
              let column = index % symbolsPerRow
              let rect = CGRect(
                  x: CGFloat(column) * symbolWidth,
                  y: CGFloat(row) * symbolWidth,
                  width: symbolWidth, height: symbolWidth)
              return rect.insetBy(dx: symbolWidth * insetProportion, dy: symbolWidth * insetProportion)
          }
      }
      
      extension CGRect {
          /// Returns a rect with the aspect ratio of `otherSize`, fitting within `self`.
          func fit(_ otherSize: CGSize) -> CGRect {
              let scale = min(size.width / otherSize.width, size.height / otherSize.height)
              let newSize = CGSize(width: otherSize.width * scale, height: otherSize.height * scale)
              let newOrigin = CGPoint(x: midX - newSize.width/2, y: midY - newSize.height/2)
              return CGRect(origin: newOrigin, size: newSize)
          }
      }
    • 22:03 - Canvas with gesture

      struct ContentView: View {
          let symbols = Array(repeating: Symbol("swift"), count: 3166)
          @GestureState private var focalPoint: CGPoint? = nil
      
          var body: some View {
              Canvas { context, size in
                  let metrics = gridMetrics(in: size)
                  for (index, symbol) in symbols.enumerated() {
                      let rect = metrics[index]
                      let (sRect, opacity) = rect.fishEyeTransform(around: focalPoint)
      
                      context.opacity = opacity
                      let image = context.resolve(symbol.image)
                      context.draw(image, in: sRect.fit(image.size))
                  }
              }
              .gesture(DragGesture(minimumDistance: 0).updating($focalPoint) { value, focalPoint, _ in
                  focalPoint = value.location
              })
          }
      
          func gridMetrics(in size: CGSize) -> SymbolGridMetrics {
              SymbolGridMetrics(size: size, numberOfSymbols: symbols.count)
          }
      }
      
      struct Symbol: Identifiable {
          let name: String
          init(_ name: String) { self.name = name }
      
          var image: Image { Image(systemName: name) }
          var id: String { name }
      }
      
      struct SymbolGridMetrics {
          let symbolWidth: CGFloat
          let symbolsPerRow: Int
          let numberOfSymbols: Int
          let insetProportion: CGFloat
      
          init(size: CGSize, numberOfSymbols: Int, insetProportion: CGFloat = 0.1) {
              let areaPerSymbol = (size.width * size.height) / CGFloat(numberOfSymbols)
              self.symbolsPerRow = Int(size.width / sqrt(areaPerSymbol))
              self.symbolWidth = size.width / CGFloat(symbolsPerRow)
              self.numberOfSymbols = numberOfSymbols
              self.insetProportion = insetProportion
          }
      
          /// Returns the frame in the grid for the symbol at `index` position.
          /// It is not valid to pass an index less than `0` or larger than the number of symbols the grid metrics was created for.
          subscript(_ index: Int) -> CGRect {
              precondition(index >= 0 && index < numberOfSymbols)
              let row = index / symbolsPerRow
              let column = index % symbolsPerRow
              let rect = CGRect(
                  x: CGFloat(column) * symbolWidth,
                  y: CGFloat(row) * symbolWidth,
                  width: symbolWidth, height: symbolWidth)
              return rect.insetBy(dx: symbolWidth * insetProportion, dy: symbolWidth * insetProportion)
          }
      }
      
      extension CGRect {
          /// Returns a rect with the aspect ratio of `otherSize`, fitting within `self`.
          func fit(_ otherSize: CGSize) -> CGRect {
              let scale = min(size.width / otherSize.width, size.height / otherSize.height)
              let newSize = CGSize(width: otherSize.width * scale, height: otherSize.height * scale)
              let newOrigin = CGPoint(x: midX - newSize.width/2, y: midY - newSize.height/2)
              return CGRect(origin: newOrigin, size: newSize)
          }
      
          /// Returns a transformed rect and relative opacity based on a fish eye effect centered around `point`.
          /// The rectangles closer to the center of that point will be larger and brighter, and those further away will be smaller, up to a distance of `radius`.
          func fishEyeTransform(around point: CGPoint?, radius: CGFloat = 300, zoom: CGFloat = 1.0) -> (frame: CGRect, opacity: CGFloat) {
              guard let point = point else {
                  return (self, 1.0)
              }
      
              let deltaX = midX - point.x
              let deltaY = midY - point.y
              let distance = sqrt(deltaX*deltaX + deltaY*deltaY)
              let theta = atan2(deltaY, deltaX)
      
              let scaledClampedDistance = pow(min(1, max(0, distance/radius)), 0.7)
              let scale = (1.0 - scaledClampedDistance)*zoom + 0.5
      
              let newOffset = distance * (2.0 - scaledClampedDistance)*sqrt(zoom)
              let newDeltaX = newOffset * cos(theta)
              let newDeltaY = newOffset * sin(theta)
      
              let newSize = CGSize(width: size.width * scale, height: size.height * scale)
              let newOrigin = CGPoint(x: (newDeltaX + point.x) - newSize.width/2, y: (newDeltaY + point.y) - newSize.height/2)
      
              // Clamp the opacity to be 0.1 at the lowest
              let opacity = max(0.1, 1.0 - scaledClampedDistance)
              return (CGRect(origin: newOrigin, size: newSize), opacity)
          }
      }
    • 22:24 - Canvas with accessibility children

      struct ContentView: View {
          let symbols = Array(repeating: Symbol("swift"), count: 3166)
          @GestureState private var focalPoint: CGPoint? = nil
      
          var body: some View {
              Canvas { context, size in
                  let metrics = gridMetrics(in: size)
                  for (index, symbol) in symbols.enumerated() {
                      let rect = metrics[index]
                      let (sRect, opacity) = rect.fishEyeTransform(around: focalPoint)
      
                      context.opacity = opacity
                      let image = context.resolve(symbol.image)
                      context.draw(image, in: sRect.fit(image.size))
                  }
              }
              .gesture(DragGesture(minimumDistance: 0).updating($focalPoint) { value, focalPoint, _ in
                  focalPoint = value.location
              })
              .accessibilityLabel("Symbol Browser")
              .accessibilityChildren {
                  List(symbols) {
                      Text($0.name)
                  }
              }
          }
      
          func gridMetrics(in size: CGSize) -> SymbolGridMetrics {
              SymbolGridMetrics(size: size, numberOfSymbols: symbols.count)
          }
      }
      
      struct Symbol: Identifiable {
          let name: String
          init(_ name: String) { self.name = name }
      
          var image: Image { Image(systemName: name) }
          var id: String { name }
      }
      
      struct SymbolGridMetrics {
          let symbolWidth: CGFloat
          let symbolsPerRow: Int
          let numberOfSymbols: Int
          let insetProportion: CGFloat
      
          init(size: CGSize, numberOfSymbols: Int, insetProportion: CGFloat = 0.1) {
              let areaPerSymbol = (size.width * size.height) / CGFloat(numberOfSymbols)
              self.symbolsPerRow = Int(size.width / sqrt(areaPerSymbol))
              self.symbolWidth = size.width / CGFloat(symbolsPerRow)
              self.numberOfSymbols = numberOfSymbols
              self.insetProportion = insetProportion
          }
      
          /// Returns the frame in the grid for the symbol at `index` position.
          /// It is not valid to pass an index less than `0` or larger than the number of symbols the grid metrics was created for.
          subscript(_ index: Int) -> CGRect {
              precondition(index >= 0 && index < numberOfSymbols)
              let row = index / symbolsPerRow
              let column = index % symbolsPerRow
              let rect = CGRect(
                  x: CGFloat(column) * symbolWidth,
                  y: CGFloat(row) * symbolWidth,
                  width: symbolWidth, height: symbolWidth)
              return rect.insetBy(dx: symbolWidth * insetProportion, dy: symbolWidth * insetProportion)
          }
      }
      
      extension CGRect {
          /// Returns a rect with the aspect ratio of `otherSize`, fitting within `self`.
          func fit(_ otherSize: CGSize) -> CGRect {
              let scale = min(size.width / otherSize.width, size.height / otherSize.height)
              let newSize = CGSize(width: otherSize.width * scale, height: otherSize.height * scale)
              let newOrigin = CGPoint(x: midX - newSize.width/2, y: midY - newSize.height/2)
              return CGRect(origin: newOrigin, size: newSize)
          }
      
          /// Returns a transformed rect and relative opacity based on a fish eye effect centered around `point`.
          /// The rectangles closer to the center of that point will be larger and brighter, and those further away will be smaller, up to a distance of `radius`.
          func fishEyeTransform(around point: CGPoint?, radius: CGFloat = 300, zoom: CGFloat = 1.0) -> (frame: CGRect, opacity: CGFloat) {
              guard let point = point else {
                  return (self, 1.0)
              }
      
              let deltaX = midX - point.x
              let deltaY = midY - point.y
              let distance = sqrt(deltaX*deltaX + deltaY*deltaY)
              let theta = atan2(deltaY, deltaX)
      
              let scaledClampedDistance = pow(min(1, max(0, distance/radius)), 0.7)
              let scale = (1.0 - scaledClampedDistance)*zoom + 0.5
      
              let newOffset = distance * (2.0 - scaledClampedDistance)*sqrt(zoom)
              let newDeltaX = newOffset * cos(theta)
              let newDeltaY = newOffset * sin(theta)
      
              let newSize = CGSize(width: size.width * scale, height: size.height * scale)
              let newOrigin = CGPoint(x: (newDeltaX + point.x) - newSize.width/2, y: (newDeltaY + point.y) - newSize.height/2)
      
              // Clamp the opacity to be 0.1 at the lowest
              let opacity = max(0.1, 1.0 - scaledClampedDistance)
              return (CGRect(origin: newOrigin, size: newSize), opacity)
          }
      }
    • 22:48 - Canvas with TimelineView

      struct ContentView: View {
          let symbols = Array(repeating: Symbol("swift"), count: 3166)
      
          var body: some View {
              TimelineView(.animation) {
                 let time = $0.date.timeIntervalSince1970
                 Canvas { context, size in
                    let metrics = gridMetrics(in: size)
                    let focalPoint = focalPoint(at: time, in: size)
                    for (index, symbol) in symbols.enumerated() {
                      let rect = metrics[index]
                      let (sRect, opacity) = rect.fishEyeTransform(
                         around: focalPoint, at: time)
      
                       context.opacity = opacity
                       let image = context.resolve(symbol.image)
                       context.draw(image, in: sRect.fit(image.size))
                    }
                 }
              }
          }
      
          func gridMetrics(in size: CGSize) -> SymbolGridMetrics {
              SymbolGridMetrics(size: size, numberOfSymbols: symbols.count)
          }
      }
      
      struct Symbol: Identifiable {
          let name: String
          init(_ name: String) { self.name = name }
      
          var image: Image { Image(systemName: name) }
          var id: String { name }
      }
      
      struct SymbolGridMetrics {
          let symbolWidth: CGFloat
          let symbolsPerRow: Int
          let numberOfSymbols: Int
          let insetProportion: CGFloat
      
          init(size: CGSize, numberOfSymbols: Int, insetProportion: CGFloat = 0.1) {
              let areaPerSymbol = (size.width * size.height) / CGFloat(numberOfSymbols)
              self.symbolsPerRow = Int(size.width / sqrt(areaPerSymbol))
              self.symbolWidth = size.width / CGFloat(symbolsPerRow)
              self.numberOfSymbols = numberOfSymbols
              self.insetProportion = insetProportion
          }
      
          /// Returns the frame in the grid for the symbol at `index` position.
          /// It is not valid to pass an index less than `0` or larger than the number of symbols the grid metrics was created for.
          subscript(_ index: Int) -> CGRect {
              precondition(index >= 0 && index < numberOfSymbols)
              let row = index / symbolsPerRow
              let column = index % symbolsPerRow
              let rect = CGRect(
                  x: CGFloat(column) * symbolWidth,
                  y: CGFloat(row) * symbolWidth,
                  width: symbolWidth, height: symbolWidth)
              return rect.insetBy(dx: symbolWidth * insetProportion, dy: symbolWidth * insetProportion)
          }
      }
      
      extension CGRect {
          /// Returns a rect with the aspect ratio of `otherSize`, fitting within `self`.
          func fit(_ otherSize: CGSize) -> CGRect {
              let scale = min(size.width / otherSize.width, size.height / otherSize.height)
              let newSize = CGSize(width: otherSize.width * scale, height: otherSize.height * scale)
              let newOrigin = CGPoint(x: midX - newSize.width/2, y: midY - newSize.height/2)
              return CGRect(origin: newOrigin, size: newSize)
          }
      
          /// Returns a transformed rect and relative opacity based on a fish eye effect centered around `point`.
          /// The rectangles closer to the center of that point will be larger and brighter, and those further away will be smaller, up to a distance of `radius`.
          func fishEyeTransform(around point: CGPoint?, radius: CGFloat = 200, zoom: CGFloat = 3.0) -> (frame: CGRect, opacity: CGFloat) {
              guard let point = point else {
                  return (self, 1.0)
              }
      
              let deltaX = midX - point.x
              let deltaY = midY - point.y
              let distance = sqrt(deltaX*deltaX + deltaY*deltaY)
              let theta = atan2(deltaY, deltaX)
      
              let scaledClampedDistance = pow(min(1, max(0, distance/radius)), 0.7)
              let scale = (1.0 - scaledClampedDistance)*zoom + 0.5
      
              let newOffset = distance * (2.0 - scaledClampedDistance)*sqrt(zoom)
              let newDeltaX = newOffset * cos(theta)
              let newDeltaY = newOffset * sin(theta)
      
              let newSize = CGSize(width: size.width * scale, height: size.height * scale)
              let newOrigin = CGPoint(x: (newDeltaX + point.x) - newSize.width/2, y: (newDeltaY + point.y) - newSize.height/2)
      
              // Clamp the opacity to be 0.1 at the lowest
              let opacity = max(0.1, 1.0 - scaledClampedDistance)
              return (CGRect(origin: newOrigin, size: newSize), opacity)
          }
      
          /// Returns a transformed rect and relative opacity based on a fish eye effect centered around `point`, based on a preset path indexed using `time`.
          func fishEyeTransform(around point: CGPoint, at time: TimeInterval) -> (frame: CGRect, opacity: CGFloat) {
              // Arbitrary zoom and radius calculation based on time
              let zoom = cos(time) + 3.0
              let radius = ((cos(time/5) + 1)/2) * 150 + 150
              return fishEyeTransform(around: point, radius: radius, zoom: zoom)
          }
      }
      
      /// Returns a focal point within `size` based on a preset path, indexed using `time`.
      func focalPoint(at time: TimeInterval, in size: CGSize) -> CGPoint {
         let offset: CGFloat = min(size.width, size.height)/4
         let distance = ((sin(time/5) + 1)/2) * offset + offset
         let scalePoint = CGPoint(x: size.width / 2 + distance * cos(time / 2), y: size.height / 2 + distance * sin(time / 2))
         return scalePoint
      }
    • 24:10 - Privacy sensitive

      Button {
          showFavoritePicker = true
      } label: {
          VStack(alignment: .center) {
              Text("Favorite Symbol")
                  .foregroundStyle(.secondary)
              Image(systemName: favoriteSymbol)
                  .font(.title2)
                  .privacySensitive(true)
         }
      }
      .tint(.purple)
    • 24:27 - Privacy sensitive (widgets)

      VStack(alignment: .leading) {
          Text("Favorite Symbol")
              .textCase(.uppercase)
              .font(.caption.bold())
      
          ContainerRelativeShape()
              .fill(.quaternary)
              .overlay {
                  Image(systemName: favoriteSymbol)
                      .font(.system(size: 40))
                      .privacySensitive(true)
              }
      }
    • 25:03 - Materials

      struct ColorList: View {
          let symbols = Array(repeating: Symbol("swift"), count: 3166)
      
          var body: some View {
              ZStack {
                  gradientBackground
                  materialOverlay
              }
          }
      
          var materialOverlay: some View {
              VStack {
                 Text("Symbol Browser")
                    .font(.largeTitle.bold())
                 Text("\(symbols.count) symbols 🎉")
                    .foregroundStyle(.secondary)
                    .font(.title2.bold())
              }
              .padding()
              .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16.0))
          }
      
          var gradientBackground: some View {
              LinearGradient(
                  gradient: Gradient(colors: [.red, .orange, .yellow, .green, .blue, .indigo, .purple]),
                  startPoint: .leading, endPoint: .trailing)
          }
      }
      
      struct Symbol: Identifiable {
          let name: String
          init(_ name: String) { self.name = name }
      
          var image: Image { Image(systemName: name) }
          var id: String { name }
      }
    • 25:40 - Safe area inset

      struct ContentView: View {
          let newSymbols = Array(repeating: Symbol("swift"), count: 645)
          let systemColors: [Color] = [.red, .orange, .yellow, .green, .mint, .teal, .cyan, .blue, .indigo, .purple, .pink, .gray, .brown]
      
          var body: some View {
              ScrollView {
                  symbolGrid
              }
              .safeAreaInset(edge: .bottom, spacing: 0) {
                  VStack(spacing: 0) {
                      Divider()
                      VStack(spacing: 0) {
                          Text("\(newSymbols.count) new symbols")
                              .foregroundStyle(.primary)
                              .font(.body.bold())
                          Text("\(systemColors.count) system colors")
                              .foregroundStyle(.secondary)
                      }
                      .padding()
                  }
                  .background(.regularMaterial)
              }
          }
      
          var symbolGrid: some View {
              LazyVGrid(columns: [.init(.adaptive(minimum: 40, maximum: 40))]) {
                  ForEach(0 ..< newSymbols.count, id: \.self) { index in
                      newSymbols[index].image
                          .foregroundStyle(.white)
                          .frame(width: 40, height: 40)
                          .background(systemColors[index % systemColors.count])
                  }
              }
              .padding()
          }
      }
      
      struct Symbol: Identifiable {
          let name: String
          init(_ name: String) { self.name = name }
      
          var image: Image { Image(systemName: name) }
          var id: String { name }
      }
    • 26:03 - Preview orientation

      struct ColorList_Previews: PreviewProvider {
          static var previews: some View {
              ColorList()
                  .previewInterfaceOrientation(.portrait)
      
              ColorList()
                  .previewInterfaceOrientation(.landscapeLeft)
          }
      }
      
      struct ColorList: View {
          let newSymbols = Array(repeating: Symbol("swift"), count: 645)
          let systemColors: [Color] = [.red, .orange, .yellow, .green, .mint, .teal, .cyan, .blue, .indigo, .purple, .pink, .gray, .brown]
      
          var body: some View {
              ScrollView {
                  symbolGrid
              }
              .safeAreaInset(edge: .bottom, spacing: 0) {
                  VStack(spacing: 0) {
                      Divider()
                      VStack(spacing: 0) {
                          Text("\(newSymbols.count) new symbols")
                              .foregroundStyle(.primary)
                              .font(.body.bold())
                          Text("\(systemColors.count) system colors")
                              .foregroundStyle(.secondary)
                      }
                      .padding()
                  }
                  .background(.regularMaterial)
              }
          }
      
          var symbolGrid: some View {
              LazyVGrid(columns: [.init(.adaptive(minimum: 40, maximum: 40))]) {
                  ForEach(0 ..< newSymbols.count, id: \.self) { index in
                      newSymbols[index].image
                          .foregroundStyle(.white)
                          .frame(width: 40, height: 40)
                          .background(systemColors[index % systemColors.count])
                  }
              }
              .padding()
          }
      }
      
      struct Symbol: Identifiable {
          let name: String
          init(_ name: String) { self.name = name }
      
          var image: Image { Image(systemName: name) }
          var id: String { name }
      }
    • 27:06 - Hello, World!

      Text("Hello, World!")
    • 27:17 - Markdown Text: strong emphasis

      Text("**Hello**, World!")
    • 27:24 - Markdown Text: links

      Text("**Hello**, World!")
      Text("""
      Have a *happy* [WWDC](https://developer.apple.com/wwdc21/)!
      """)
    • 27:30 - Markdown Text: inline code

      Text("""
      Is this *too* meta?
      
      `Text("**Hello**, World!")`
      `Text(\"\"\"`
      `Have a *happy* [WWDC](https://developer.apple.com/wwdc21/)!`
      `\"\"\")`
      """)
    • 27:37 - AttributedString

      struct ContentView: View {
          var body: some View {
              Text(formattedDate)
          }
      
          var formattedDate: AttributedString {
              var formattedDate: AttributedString = Date().formatted(Date.FormatStyle().day().month(.wide).weekday(.wide).attributed)
      
              let weekday = AttributeContainer.dateField(.weekday)
              let color = AttributeContainer.foregroundColor(.orange)
              formattedDate.replaceAttributes(weekday, with: color)
      
              return formattedDate
          }
      }
    • 29:17 - Text selection

      struct ContentView: View {
          var activity: Activity = .sample
      
          var body: some View {
              VStack(alignment: .leading, spacing: 0) {
                  ActivityHeader(activity)
                  Divider()
                  Text(activity.info)
                      .textSelection(.enabled)
                      .padding()
                  Spacer()
              }
              .background()
              .navigationTitle(activity.name)
          }
      }
      
      struct ActivityHeader: View {
          var activity: Activity
          init(_ activity: Activity) { self.activity = activity }
      
          var body: some View {
              VStack(alignment: alignment.horizontal, spacing: 8) {
                  HStack(alignment: .firstTextBaseline) {
      #if os(macOS)
                      Text(activity.name)
                          .font(.title2.bold())
                      Spacer()
      #endif
                      Text(activity.date.formatted(.dateTime.weekday(.wide).day().month().hour().minute()))
                          .foregroundStyle(.secondary)
                  }
                  HStack(alignment: .firstTextBaseline) {
                      Image(systemName: "person.2")
                      Text(activity.people.map(\.nameComponents).formatted(.list(memberStyle: .name(style: .short), type: .and)))
                  }
              }
      #if os(macOS)
              .padding()
      #else
              .padding([.horizontal, .bottom])
      #endif
              .frame(maxWidth: .infinity, alignment: alignment)
              .background(activity.tint.opacity(0.1).ignoresSafeArea())
          }
      
          private var alignment: Alignment {
      #if os(macOS)
              .leading
      #else
              .center
      #endif
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      }
      
      struct Person {
          var givenName: String
          var familyName: String = ""
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 29:28 - Text selection: view hierarchy

      struct ContentView: View {
          var activity: Activity = .sample
      
          var body: some View {
              VStack(alignment: .leading, spacing: 0) {
                  ActivityHeader(activity)
                  Divider()
                  Text(activity.info)
                      .padding()
                  Spacer()
              }
              .textSelection(.enabled)
              .background()
              .navigationTitle(activity.name)
          }
      }
      
      struct ActivityHeader: View {
          var activity: Activity
          init(_ activity: Activity) { self.activity = activity }
      
          var body: some View {
              VStack(alignment: alignment.horizontal, spacing: 8) {
                  HStack(alignment: .firstTextBaseline) {
      #if os(macOS)
                      Text(activity.name)
                          .font(.title2.bold())
                      Spacer()
      #endif
                      Text(activity.date.formatted(.dateTime.weekday(.wide).day().month().hour().minute()))
                          .foregroundStyle(.secondary)
                  }
                  HStack(alignment: .firstTextBaseline) {
                      Image(systemName: "person.2")
                      Text(activity.people.map(\.nameComponents).formatted(.list(memberStyle: .name(style: .short), type: .and)))
                  }
              }
      #if os(macOS)
              .padding()
      #else
              .padding([.horizontal, .bottom])
      #endif
              .frame(maxWidth: .infinity, alignment: alignment)
              .background(activity.tint.opacity(0.1).ignoresSafeArea())
          }
      
          private var alignment: Alignment {
      #if os(macOS)
              .leading
      #else
              .center
      #endif
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      }
      
      struct Person {
          var givenName: String
          var familyName: String = ""
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 30:03 - Text formatting: List

      struct ContentView: View {
          var activity: Activity = .sample
      
          var body: some View {
              Text(activity.people.map(\.nameComponents).formatted(.list(memberStyle: .name(style: .short), type: .and)))
                  .scenePadding()
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      }
      
      struct Person {
          var givenName: String
          var familyName: String = ""
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 30:43 - Text field formatting

      struct ContentView: View {
          @State private var newAttendee = PersonNameComponents()
      
          var body: some View {
             TextField("New Person", value: $newAttendee,
                format: .name(style: .medium))
          }
      }
    • 31:09 - Text field prompts and labels

      struct ContentView: View {
          @State var activity: Activity = .sample
      
          var body: some View {
              Form {
                  TextField("Name:", text: $activity.name, prompt: Text("New Activity"))
                  TextField("Location:", text: $activity.location)
                  DatePicker("Date:", selection: $activity.date)
              }
              .frame(minWidth: 250)
              .padding()
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      }
      
      struct Person {
          var givenName: String
          var familyName: String = ""
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 31:39 - Text field submission

      struct ContentView: View {
          @State private var activity: Activity = .sample
          @State private var newAttendee = PersonNameComponents()
      
          var body: some View {
              TextField("New Person", value: $newAttendee,
                  format: .name(style: .medium)
              )
              .onSubmit {
                  activity.append(Person(newAttendee))
                  newAttendee = PersonNameComponents()
              }
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 31:59 - Text field submission: submit label

      struct ContentView: View {
          @State private var activity: Activity = .sample
          @State private var newAttendee = PersonNameComponents()
      
          var body: some View {
              TextField("New Person", value: $newAttendee,
                  format: .name(style: .medium)
              )
              .onSubmit {
                  activity.append(Person(newAttendee))
                  newAttendee = PersonNameComponents()
              }
              .submitLabel(.done)
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 32:07 - Keyboard toolbar

      struct ContentView: View {
          @State private var activity: Activity = .sample
          @FocusState private var focusedField: Field?
      
          var body: some View {
              Form {
                  TextField("Name", text: $activity.name, prompt: Text("New Activity"))
                  TextField("Location", text: $activity.location)
                  DatePicker("Date", selection: $activity.date)
              }
              .toolbar {
                  ToolbarItemGroup(placement: .keyboard) {
                      Button(action: selectPreviousField) {
                          Label("Previous", systemImage: "chevron.up")
                      }
                      .disabled(!hasPreviousField)
      
                      Button(action: selectNextField) {
                          Label("Next", systemImage: "chevron.down")
                      }
                      .disabled(!hasNextField)
                  }
              }
          }
      
          private func selectPreviousField() {
             focusedField = focusedField.map {
                Field(rawValue: $0.rawValue - 1)!
             }
          }
      
          private var hasPreviousField: Bool {
              if let currentFocusedField = focusedField {
                  return currentFocusedField.rawValue > 0
              } else {
                  return false
              }
          }
      
          private func selectNextField() {
             focusedField = focusedField.map {
                Field(rawValue: $0.rawValue + 1)!
             }
          }
      
          private var hasNextField: Bool {
              if let currentFocusedField = focusedField {
                  return currentFocusedField.rawValue < Field.allCases.count
              } else {
                  return false
              }
          }
      }
      
      private enum Field: Int, Hashable, CaseIterable {
         case name, location, date, addAttendee
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 33:05 - Focus state

      struct ContentView: View {
          @State private var activity: Activity = .sample
          @State private var newAttendee = PersonNameComponents()
          @FocusState private var addAttendeeIsFocused: Bool
      
          var body: some View {
              VStack {
                  Form {
                      TextField("Name:", text: $activity.name, prompt: Text("New Activity"))
                      TextField("Location:", text: $activity.location)
                      DatePicker("Date:", selection: $activity.date)
                  }
      
                  TextField("New Person", value: $newAttendee, format: .name(style: .medium))
                      .focused($addAttendeeIsFocused)
              }
              .frame(minWidth: 250)
              .scenePadding()
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 33:16 - Focus state: setting focus

      struct ContentView: View {
          @State private var activity: Activity = .sample
          @State private var newAttendee = PersonNameComponents()
          @FocusState private var addAttendeeIsFocused: Bool
      
          var body: some View {
              VStack {
                  Form {
                      TextField("Name:", text: $activity.name, prompt: Text("New Activity"))
                      TextField("Location:", text: $activity.location)
                      DatePicker("Date:", selection: $activity.date)
                  }
      
                  VStack(alignment: .leading) {
                      TextField("New Person", value: $newAttendee, format: .name(style: .medium))
                          .focused($addAttendeeIsFocused)
      
                      ControlGroup {
                          Button {
                              addAttendeeIsFocused = true
                          } label: {
                             Label("Add Attendee", systemImage: "plus")
                          }
                      }
                      .fixedSize()
                  }
              }
              .frame(minWidth: 250)
              .scenePadding()
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 33:30 - Focus state: Hashable value

      private enum Field: Int, Hashable, CaseIterable {
         case name, location, date, addAttendee
      }
      
      struct ContentView: View {
          @State private var activity: Activity = .sample
          @State private var newAttendee = PersonNameComponents()
          @FocusState private var focusedField: Field?
      
          var body: some View {
              VStack {
                  Form {
                      TextField("Name:", text: $activity.name, prompt: Text("New Activity"))
                          .focused($focusedField, equals: .name)
                      TextField("Location:", text: $activity.location)
                          .focused($focusedField, equals: .location)
                      DatePicker("Date:", selection: $activity.date)
                          .focused($focusedField, equals: .date)
                  }
      
                  VStack(alignment: .leading) {
                      TextField("New Person", value: $newAttendee, format: .name(style: .medium))
                          .focused($focusedField, equals: .addAttendee)
      
                      ControlGroup {
                          Button {
                              focusedField = .addAttendee
                          } label: {
                             Label("Add Attendee", systemImage: "plus")
                          }
                      }
                      .fixedSize()
                  }
              }
              .frame(minWidth: 250)
              .scenePadding()
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 34:03 - Focus state: back/forward controls

      private enum Field: Int, Hashable, CaseIterable {
         case name, location, date, addAttendee
      }
      
      struct ContentView: View {
          @State private var activity: Activity = .sample
          @FocusState private var focusedField: Field?
      
          var body: some View {
              Form {
                  TextField("Name", text: $activity.name, prompt: Text("New Activity"))
                  TextField("Location", text: $activity.location)
                  DatePicker("Date", selection: $activity.date)
              }
              .toolbar {
                  ToolbarItemGroup(placement: .keyboard) {
                      Button(action: selectPreviousField) {
                          Label("Previous", systemImage: "chevron.up")
                      }
                      .disabled(!canSelectPreviousField)
      
                      Button(action: selectNextField) {
                          Label("Next", systemImage: "chevron.down")
                      }
                      .disabled(!canSelectNextField)
                  }
              }
          }
      
          private func selectPreviousField() {
             focusedField = focusedField.map {
                Field(rawValue: $0.rawValue - 1)!
             }
          }
      
          private var canSelectPreviousField: Bool {
              if let currentFocusedField = focusedField {
                  return currentFocusedField.rawValue > 0
              } else {
                  return false
              }
          }
      
          private func selectNextField() {
             focusedField = focusedField.map {
                Field(rawValue: $0.rawValue + 1)!
             }
          }
      
          private var canSelectNextField: Bool {
              if let currentFocusedField = focusedField {
                  return currentFocusedField.rawValue < Field.allCases.count
              } else {
                  return false
              }
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 34:13 - Focus state: keyboard dismissal

      private enum Field: Int, Hashable, CaseIterable {
         case name, location, date, addAttendee
      }
      
      struct ContentView: View {
          @State private var activity: Activity = .sample
          @FocusState private var focusedField: Field?
      
          var body: some View {
              Form {
                  TextField("Name", text: $activity.name, prompt: Text("New Activity"))
                  TextField("Location", text: $activity.location)
                  DatePicker("Date", selection: $activity.date)
              }
          }
      
          func endEditing() {
              focusedField = nil
          }
      }
      
      struct Activity {
          var name: String
          var date: Date
          var location: String
          var people: [Person]
          var info: AttributedString
          var tint: Color = .purple
      
          static let sample = Activity(name: "What's New in SwiftUI", date: Date(), location: "Apple Park", people: [.init(givenName: "You")], info: "This is some info.")
      
          mutating func append(_ person: Person) {
              people.append(person)
          }
      }
      
      struct Person {
          var givenName: String
          var familyName: String
      
          init(givenName: String, familyName: String = "") {
              self.givenName = givenName
              self.familyName = familyName
          }
      
          init(_ nameComponents: PersonNameComponents) {
              givenName = nameComponents.givenName ?? ""
              familyName = nameComponents.familyName ?? ""
          }
      
          var nameComponents: PersonNameComponents {
              get {
                  var components = PersonNameComponents()
                  components.givenName = givenName
                  if !familyName.isEmpty {
                      components.familyName = familyName
                  }
                  return components
              }
              set {
                  givenName = newValue.givenName ?? ""
                  familyName = newValue.familyName ?? ""
              }
          }
      }
    • 34:55 - Bordered buttons

      Button("Add") {
         // ... 
      }
      .buttonStyle(.bordered)
    • 35:03 - Bordered buttons: view hierarchy

      struct ContentView: View {
          var body: some View {
              ScrollView {
                  LazyVStack {
                      ForEach(0..<10) { _ in
                          Button("Add") {
                              //...
                          }
                      }
                  }
              }
              .buttonStyle(.bordered)
          }
      }
    • 35:09 - Bordered buttons: tinting

      struct ContentView: View {
          var body: some View {
              ScrollView {
                  LazyVStack {
                      ForEach(0..<10) { _ in
                          Button("Add") {
                              //...
                          }
                      }
                  }
              }
              .buttonStyle(.bordered)
              .tint(.green)
          }
      }
    • 35:16 - Control size and prominence

      struct ContentView: View {
          var entry: ButtonEntry = .sample
      
          var body: some View {
              HStack {
                  ForEach(entry.tags) { tag in
                      Button(tag.name) {
                          // ...
                      }
                      .tint(tag.color)
                  }
              }
              .buttonStyle(.bordered)
              .controlSize(.small)
              .controlProminence(.increased)
          }
      }
      
      struct ButtonEntry {
          struct Tag: Identifiable {
              var name: String
              var color: Color
              var id: String { name }
          }
      
          var name: String
          var tags: [Tag]
      
          static let sample = ButtonEntry(name: "Stroopwafel", tags: [Tag(name: "1960s", color: .purple), Tag(name: "bronze", color: .yellow)])
      }
    • 35:34 - Large buttons

      struct ContentView: View {
          var body: some View {
              VStack {
                  Button(action: addToJar) {
                      Text("Add to Jar").frame(maxWidth: 300)
                  }
                  .controlProminence(.increased)
                  .keyboardShortcut(.defaultAction)
      
                  Button(action: addToWatchlist) {
                      Text("Add to Watchlist").frame(maxWidth: 300)
                  }
                  .tint(.accentColor)
              }
              .buttonStyle(.bordered)
              .controlSize(.large)
          }
      
          private func addToJar() {}
          private func addToWatchlist() {}
      }
    • 37:14 - Destructive buttons

      struct ContentView: View {
          var entry: ButtonEntry = .sample
      
          var body: some View {
              ButtonEntryCell(entry)
                  .contextMenu {
                      Section {
                          Button("Open") {
                              // ...
                          }
                          Button("Delete...", role: .destructive) {
                              // ...
                          }
                      }
      
                      Section {
                          Button("Archive") {}
      
                          Menu("Move to") {
                              ForEach(Jar.allJars) { jar in
                                  Button("\(jar.name)") {
                                      //addTo(jar)
                                  }
                              }
                          }
                      }
                  }
          }
      }
      
      struct ButtonEntryCell: View {
          var entry: ButtonEntry = .sample
          init(_ entry: ButtonEntry) { self.entry = entry }
      
          var body: some View {
              Text(entry.name)
                  .padding()
          }
      }
      
      struct Jar: Identifiable {
          var name: String
          var id: String { name }
      
          static let allJars = [Jar(name: "Secret Stash")]
      }
      
      struct ButtonEntry {
          struct Tag: Identifiable {
              var name: String
              var color: Color
              var id: String { name }
          }
      
          var name: String
          var tags: [Tag]
      
          static let sample = ButtonEntry(name: "Stroopwafel", tags: [Tag(name: "1960s", color: .purple), Tag(name: "bronze", color: .yellow)])
      }
    • 37:25 - Confirmation dialogs

      struct ContentView: View {
          var entry: ButtonEntry = .sample
          @State private var showConfirmation: Bool = false
      
          var body: some View {
              ButtonEntryCell(entry)
                  .contextMenu {
                      Section {
                          Button("Open") {
                              // ...
                          }
                          Button("Delete...", role: .destructive) {
                              showConfirmation = true
                              // ...
                          }
                      }
      
                      Section {
                          Button("Archive") {}
      
                          Menu("Move to") {
                              ForEach(Jar.allJars) { jar in
                                  Button("\(jar.name)") {
                                      //addTo(jar)
                                  }
                              }
                          }
                      }
                  }
                  .confirmationDialog(
                      "Are you sure you want to delete \(entry.name)?",
                      isPresented: $showConfirmation
                  ) {
                      Button("Delete", role: .destructive) {
                          // delete the entry
                      }
                  } message: {
                      Text("Deleting \(entry.name) will remove it from all of your jars.")
                  }
          }
      }
      
      struct ButtonEntryCell: View {
          var entry: ButtonEntry = .sample
          init(_ entry: ButtonEntry) { self.entry = entry }
      
          var body: some View {
              Text(entry.name)
                  .padding()
          }
      }
      
      struct Jar: Identifiable {
          var name: String
          var id: String { name }
      
          static let allJars = [Jar(name: "Secret Stash")]
      }
      
      struct ButtonEntry {
          struct Tag: Identifiable {
              var name: String
              var color: Color
              var id: String { name }
          }
      
          var name: String
          var tags: [Tag]
      
          static let sample = ButtonEntry(name: "Stroopwafel", tags: [Tag(name: "1960s", color: .purple), Tag(name: "bronze", color: .yellow)])
      }
    • 37:59 - Menu buttons

      struct ContentView: View {
          var buttonEntry: ButtonEntry = .sample
          @StateObject private var jarStore = JarStore()
      
          var body: some View {
              Menu("Add") {
                 ForEach(jarStore.allJars) { jar in
                    Button("Add to \(jar.name)") {
                       jarStore.add(buttonEntry, to: jar)
                    }
                 }
              }
              .menuStyle(BorderedButtonMenuStyle())
              .scenePadding()
          }
      }
      
      class JarStore: ObservableObject {
          var allJars: [Jar] = Jar.allJars
          func add(_ entry: ButtonEntry, to jar: Jar) {}
      }
      
      struct Jar: Identifiable {
          var name: String
          var id: String { name }
          static let allJars = [Jar(name: "Secret Stash")]
      }
      
      struct ButtonEntry {
          var name: String
          static let sample = ButtonEntry(name: "Stroopwafel")
      }
    • 38:10 - Menu buttons: hidden indicator

      struct ContentView: View {
          var buttonEntry: ButtonEntry = .sample
          @StateObject private var jarStore = JarStore()
      
          var body: some View {
              Menu("Add") {
                 ForEach(jarStore.allJars) { jar in
                    Button("Add to \(jar.name)") {
                       jarStore.add(buttonEntry, to: jar)
                    }
                 }
              }
              .menuStyle(BorderedButtonMenuStyle())
              .menuIndicator(.hidden)
              .scenePadding()
          }
      }
      
      class JarStore: ObservableObject {
          var allJars: [Jar] = Jar.allJars
          func add(_ entry: ButtonEntry, to jar: Jar) {}
      }
      
      struct Jar: Identifiable {
          var name: String
          var id: String { name }
          static let allJars = [Jar(name: "Secret Stash")]
      }
      
      struct ButtonEntry {
          var name: String
          static let sample = ButtonEntry(name: "Stroopwafel")
      }
    • 38:31 - Menu buttons: primary action

      struct ContentView: View {
          var buttonEntry: ButtonEntry = .sample
          @StateObject private var jarStore = JarStore()
      
          var body: some View {
              Menu("Add") {
                 ForEach(jarStore.allJars) { jar in
                    Button("Add to \(jar.name)") {
                       jarStore.add(buttonEntry, to: jar)
                    }
                 }
              } primaryAction: {
                  jarStore.addToDefaultJar(buttonEntry)
              }
              .menuStyle(BorderedButtonMenuStyle())
              .scenePadding()
          }
      }
      
      class JarStore: ObservableObject {
          var allJars: [Jar] = Jar.allJars
          func add(_ entry: ButtonEntry, to jar: Jar) {}
          func addToDefaultJar(_ entry: ButtonEntry) {}
      }
      
      struct Jar: Identifiable {
          var name: String
          var id: String { name }
          static let allJars = [Jar(name: "Secret Stash")]
      }
      
      
      struct ButtonEntry {
          var name: String
          static let sample = ButtonEntry(name: "Stroopwafel")
      }
    • 38:42 - Menu buttons: primary action, indicator hidden

      struct ContentView: View {
          var buttonEntry: ButtonEntry = .sample
          @StateObject private var jarStore = JarStore()
      
          var body: some View {
              Menu("Add") {
                 ForEach(jarStore.allJars) { jar in
                    Button("Add to \(jar.name)") {
                       jarStore.add(buttonEntry, to: jar)
                    }
                 }
              } primaryAction: {
                  jarStore.addToDefaultJar(buttonEntry)
              }
              .menuStyle(BorderedButtonMenuStyle())
              .menuIndicator(.hidden)
              .scenePadding()
          }
      }
      
      class JarStore: ObservableObject {
          var allJars: [Jar] = Jar.allJars
          func add(_ entry: ButtonEntry, to jar: Jar) {}
          func addToDefaultJar(_ entry: ButtonEntry) {}
      }
      
      struct Jar: Identifiable {
          var name: String
          var id: String { name }
          static let allJars = [Jar(name: "Secret Stash")]
      }
      
      
      struct ButtonEntry {
          var name: String
          static let sample = ButtonEntry(name: "Stroopwafel")
      }
    • 39:01 - Toggle buttons

      Toggle(isOn: $showOnlyNew) {
          Label("Show New Buttons", systemImage: "sparkles")
      }
      .toggleStyle(.button)
    • 39:13 - Control group

      ControlGroup {
          Button(action: archive) {
              Label("Archive", systemImage: "archiveBox")
          }
          Button(action: delete) {
              Label("Delete", systemName: "trash")
          }
      }
    • 39:26 - Control group: back/forward control

      struct ContentView: View {
          @State var current: String = "More buttons"
          @State var history: [String] = ["Text and keyboard", "Advanced graphics", "Beyond lists", "Better lists"]
          @State var forwardHistory: [String] = []
      
          var body: some View {
              Color.clear
                  .toolbar{
                      ToolbarItem(placement: .navigation) {
                          ControlGroup {
                              Menu {
                                  ForEach(history, id: \.self) { previousSection in
                                      Button(previousSection) {
                                          goBack(to: previousSection)
                                      }
                                  }
                              } label: {
                                  Label("Back", systemImage: "chevron.backward")
                              } primaryAction: {
                                  goBack(to: history[0])
                              }
                              .disabled(history.isEmpty)
      
                              Menu {
                                  ForEach(forwardHistory, id: \.self) { nextSection in
                                      Button(nextSection) {
                                          goForward(to: nextSection)
                                      }
                                  }
                              } label: {
                                  Label("Forward", systemImage: "chevron.forward")
                              } primaryAction: {
                                  goForward(to: forwardHistory[0])
                              }
                              .disabled(forwardHistory.isEmpty)
                          }
                          .controlGroupStyle(.navigation)
                      }
                  }
                  .navigationTitle(current)
          }
      
          private func goBack(to section: String) {
              guard let index = history.firstIndex(of: section) else { return }
              forwardHistory.insert(current, at: 0)
              forwardHistory.insert(contentsOf: history[...history.index(before: index)].reversed(), at: 0)
              history.removeSubrange(...index)
              current = section
          }
      
          private func goForward(to section: String) {
              guard let index = forwardHistory.firstIndex(of: section) else { return }
              history.insert(current, at: 0)
              history.insert(contentsOf: forwardHistory[...forwardHistory.index(before: index)].reversed(), at: 0)
              forwardHistory.removeSubrange(...index)
              current = section
          }
      }

Developer Footer

  • Vidéos
  • WWDC21
  • What's new in SwiftUI
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines