View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Videos

Open Menu Close Menu
  • Collection
  • Topics
  • All Videos
  • About

More Videos

Streaming is available in most browsers,
and in the Developer app.

  • Overview
  • Transcript
  • Code
  • What’s new in SwiftUI

    Learn how you can use SwiftUI to build great apps for any Apple platform. Explore a fresh new look and feel for tabs and documents on iPadOS. Improve your window management with new windowing APIs, and gain more control over immersive spaces and volumes in your visionOS apps. We'll also take you through other exciting refinements that help you make expressive charts, customize and layout text, and so much more.

    Chapters

    • 0:00 - Introduction
    • 0:51 - Fresh apps
    • 1:04 - Fresh apps: TabView
    • 2:22 - Fresh apps: Presentation sizing
    • 2:39 - Fresh apps: Zoom transition
    • 3:02 - Fresh apps: Custom controls
    • 3:38 - Fresh apps: Vectorized and function plots
    • 4:10 - Fresh apps: TableColumnForEach
    • 4:25 - Fresh apps: MeshGradient
    • 4:51 - Fresh apps: Document launch experience
    • 5:33 - Fresh apps: SF Symbols 6
    • 6:37 - Harnessing the platform
    • 6:52 - Harnessing the platform: Windowing
    • 8:28 - Harnessing the platform: Input methods
    • 10:45 - Harnessing the platform: Widgets and Live Activities
    • 12:25 - Intermezzo
    • 12:55 - Framework foundations
    • 13:09 - Framework foundations: Custom containers
    • 13:48 - Framework foundations: Ease of use
    • 16:18 - Framework foundations: Scrolling enhancements
    • 17:18 - Framework foundations: Swift 6 language mode
    • 18:01 - Framework foundations: Improved interoperability
    • 19:18 - Crafting experiences
    • 19:43 - Crafting experiences: Volumes
    • 20:27 - Crafting experiences: Immersive spaces
    • 21:27 - Crafting experiences: TextRenderer
    • 22:12 - Next steps

    Resources

    • Forum: UI Frameworks
    • SwiftUI updates
      • HD Video
      • SD Video

    Related Videos

    WWDC24

    • Bring your Live Activity to Apple Watch
    • Catch up on accessibility in SwiftUI
    • Create custom hover effects in visionOS
    • Create custom visual effects with SwiftUI
    • Demystify SwiftUI containers
    • Dive deep into volumes and immersive spaces
    • Elevate your tab and sidebar experience in iPadOS
    • Enhance your UI animations and transitions
    • Evolve your document launch experience
    • Extend your app’s controls across the system
    • Migrate your app to Swift 6
    • Squeeze the most out of Apple Pencil
    • Swift Charts: Vectorized and function plots
    • Tailor macOS windows with SwiftUI
    • What’s new in SF Symbols 6
    • Work with windows in SwiftUI
  • Download

    Hi, and thanks for tuning in! I’m Sommer! And I’m Sam! We’re both engineers on the SwiftUI team. And we’re excited to tell you all about what’s new in SwiftUI.

    To help with our karaoke habit, Sam and I have been working on an app to plan our regular team karaoke parties.

    Our app harnesses so many improvements to SwiftUI, and we're excited to share those improvements with you! Starting with plenty of great features to make your apps feel brand new. Tools to refine your apps so they feel at home on every platform. Widespread improvements to the foundational building blocks of the framework. And a whole suite of new tools for crafting immersive experiences. Whew! That’s a lot to cover, so let’s dive right in. You can really freshen up your apps with SwiftUI, from a new tab view, to beautiful mesh gradients, and snappy controls! Sam and I wrote a karaoke event planner app. It’s primarily a side bar driven app, and in iOS 18.0, the sidebar has become a lot more flexible. With just the touch of a button, our main-view changes to a tab bar representation for showing more of that beautiful UI. The tab bar now floats above the content! People can even customize the experience completely to their liking, reordering the items and hiding infrequently used options.

    It was easy to rewrite my main view to use the new tabview.

    Now, TabView has a new type-safe syntax in SwiftUI to make it easier to catch common errors at build time. And, as your content grows, it’s simple to make your tab view more flexible. By just applying the new .sidebarAdaptable tab view style, karaoke planners can now switch between the tab bar and sidebar view.

    The sidebar looks great when you have a lot of content, like all these song lists, and it even has customization behavior like reordering and removing tabs, which is completely programmatically controllable. The refreshed side bar also looks great on tvOS, and you can style the tab view to appear as a sidebar or as a segmented control in the toolbar on macOS! Sheet presentation sizing is now unified and simplified across platforms. You can use the .presentationSizing modifier to create perfectly-sized sheets with .form or .page, or even use custom sizing. SwiftUI supports a new zoom navigation transition too, and I can use it to make expanding info on a party look gorgeous.

    For more on the new TabView check out "Improve your tab and sidebar experience on iPad". And to dive deeper into new animations watch "Enhance your UI animations and transitions".

    You can now create your own custom resizable controls such as buttons and toggles that live in control center or the lock screen and can even be activated by the action button. Controls are a new kind of Widget that that are easy to build with App Intents! With just a few lines of code, I can create a ControlWidgetButton to get the WWDC karaoke party started at a moment’s notice! To learn how to use the new and powerful controls API to customize your configurable buttons and toggles, check out: "Access your app's controls across the system." Sam and I have been working hard to grow our karaoke party attendance. I think an exponential function seems like very reasonable goal.

    Function plotting in Swift charts makes it easy for me draw beautiful graphs, like this LinePlot.

    I’ll go ahead and graph the actual attendance too.

    Oof, not quite there yet.

    To dig deeper into the improvements in Swift Charts, catch "Swift Charts: Vectorized and function plots".

    As a data nerd, I also like to keep track of how much the attendees are singing at the parties.

    Using TableColumnForEach, I can now have a dynamic number of table columns for however many parties Sam and I have! To boost our attendance numbers, I think we should send out some colorful party invites! SwiftUI has added first class support for colorful mesh gradients. By interpolating between the points on a grid of colors, you can create a beautiful lattice! This sounds like the perfect way make our karaoke invites as snazzy as our parties! Now that the Karaoke Planner App is looking fresh, Sam and I want to have a little extra fun by customizing our lyrics! So, we’ve also built a document-based app for editing the words to our favorite songs. I crafted this launch screen to express my app’s individuality and highlight its features using the new Document Launch Scene type.

    I created a big bold title, customized the background and added some fun accessory views to really make my launch experience pop! To learn more about all you can do with document based apps, like custom document icons and templates watch “Evolve your document launch experience” I’m going to put a finishing touch on my launch screen now, using a symbol effect on my music notes! Wow, my symbols really do wiggle wiggle.

    Apps can now adopt three new animation presets for SF Symbols. The wiggle effect oscillates a symbol in any direction or angle to draw attention.

    The breathe effect smoothly scales a symbol up and down to indicate ongoing activity. And the rotate effect spins some parts of a symbol around a designated anchor point.

    Some of the existing presets have also been enhanced with new features. For example, the default Replace animation now prefers a new MagicReplace behavior. With MagicReplace, symbols smoothly animate badges and slashes.

    These are just a few of the enhancements in SF Symbols 6. To learn more watch “What’s new in SF Symbols”.

    SwiftUI brings great improvements to making your apps feel at home on any of Apple’s platforms. With improved windowing, more control over input and lots of glanceable content, your apps can take advantage of whatever platform they’re on. You can now tailor the style and behavior of windows on macOS.

    In my lyric editor app on macOS, I have a window that shows a single line preview. I used the new plain window style to remove the default window chrome. And gave it a floating window level so it shows on top of any other windows. I used the defaultWindowPlacement API to place it at the top of the screen, so it doesn't cover-up the rest of my lyrics; I took into account the size of the display, and the size of the content, to place it in the perfect spot.

    I also added a WindowDragGesture to the content view of the preview so I can adjust its position on screen by dragging it around.

    There are new scene types too, like utility window! To learn more about ways to customize the style and behavior of your windows, watch “Tailor macOS windows with SwiftUI!”.

    This multi-window lyrics editor app works great on visionOS too! Recently my fellow karaoke pal Andrew showed me how he’s using this new push window action in the Botanist app to help him keep his most important content focused! pushWindow can be used to open a window and hide the originating window. I definitely want to do the same thing in my Lyrics editor, so I’ll use the pushWindow environment action to focus on my lyric preview. Learn more in the video: "Work with windows in SwiftUI". SwiftUI gives you many new tools for taking advantage of the unique input methods each platform offers.

    In visionOS, you can make views react when people look at them, place a finger near them, or move the pointer over them, all while preserving people’s privacy.

    Within the new closure-based hoverEffect modifier, I can control how the view looks as it transitions between the active and IN-active states To learn how you can also coordinate multiple effects, control the timing of an effect, and respond to accessibility settings, check out “Create custom hover effects in visionOS.” Great iPadOS, macOS, and visionOS apps provide great keyboard support.

    In the main menu on macOS, I have an item to open my preview window.

    I've added the new modifierKeyAlternate modifier, and on press of the option key I can reveal a secondary item to preview in full-screen.

    For low level control, any view can respond to modifier key press state changes. I updated the lyric editor to use onModifierKeysChanged. So when I hold down option, I can see and adjust an extra alignment guide for where the bouncing ball lands on the lyrics.

    Pointer interaction is another important form of input across many devices.

    The pointerStyle API lets you customize the appearance and visibility of the system pointer. Since I made my lyrics resizable, I’ll apply an appropriate frameResize pointer style on each resize anchor.

    New in iPadOS 17.5, SwiftUI has support for features of Apple Pencil and Apple Pencil Pro like double-tap and squeeze.

    With .onPencilSqueeze, I can gather information from the gesture and see what action is preferred. In this case, I’m going to show my lyrics doodle palette under the pencil’s hover location so I can mark up lyrics with fun drawings. To learn more about all the new Apple Pencil APIs, head over to "Squeeze the most out of Apple Pencil". Widgets offer glanceable information and key interactions with your apps. And now that Live Activities have also come to watchOS, your iOS based live activities will automatically show up on Apple Watch, without any work on your part! Sam and I already have this live activity to review song lyrics on the go, and I can see it showing up automatically on my Apple Watch! To make it look really amazing, I can use the new small .supplementalActivityFamily to tailor the content for watchOS, showing more lyrics at once. Nice! And to allow our singers to advance the lyrics using double tap, I can apply the .handGestureShortcut modifier.

    I also want to be sure that I always know when I’m up next at a karaoke event, so I added a widget to my Planner app using the new reference date format style to show a countdown to Karaoke o’ clock! Text now has additional formats for the display of live times and dates that work great in widgets and live activities! These formats include date references, date offsets and timers. Each format is deeply customizable down to its components, and can even adapt to the size of its container.

    Widgets are smarter now too. Specify relevant contexts so the system can more intelligently surface them in places like smart stacks. With this, my countdown widget can show up automatically at a specified time, or when a singer gets close to a planned karaoke venue! Hey Sommer, How’s the lyrics editor coming along? I’ve been thinking up lyrics for my next solo, "Cupertino Dreamin", and I’ve gotta get it written down before I forget! It’s going great! I just used it to write some lyrics for “Smells Like Scene Spirit”, it’s gonna rock. Nice, we can try to slot that in towards the end of the setlist. That reminds me: the setlist! Is it gonna be ready for the WWDC karaoke party? You bet Sommer. The theme for the songs is all of SwiftUI’s great new Framework foundations. SwiftUI has added all sorts of new API that make working with the framework feel easier than ever, Including improvements to the core components of SwiftUI, new ways to use foundational APIs, and ease of use enhancements.

    You can now create your own custom container views. A new API on ForEach, subviewOf, lets you to iterate over the subviews of a given view, like in this example where I'm wrapping each subview in its own card view. You can use this to make custom containers that have the same capabilities as SwiftUI’s built in containers like list and picker, including mixing static, and dynamic content supporting sections, and adding container specific modifiers. To learn more about custom containers and the SwiftUI fundamentals behind them, be sure to check out "Demystify SwiftUI Containers". New ease of use improvements make working with SwiftUI easier than ever. Instead of having to write out a full conformance to EnvironmentKey, and an extension on environment values, you can now just write a simple property with the new Entry macro. Best of all, it doesn’t just work with environment values. The entry macro can also be used with FocusValues, Transaction, and the new ContainerValues as well.

    You can now attach additional information to SwiftUI's built in accessibility labels. That means you can add additional accessibility information to a control, without overriding the framework provided label. Be sure to check out "Catch up on accessibility in SwiftUI" to learn about all of SwiftUI’s amazing new accessibility features, such as conditional modifier support, and app intent based accessibilityActions.

    Xcode previews has a new dynamic linking architecture that allows you to switch between a preview, and build-and-run without needing to re-build your project, increasing your iteration speed.

    And it’s now easier to set up Previews too. You can now use State directly in previews using the Previewable macro, eliminating the boilerplate of wrapping your preview content in a view.

    There are now new ways to work with text and manage selection. SwiftUI now offers programatic access to, and control of text selection within text editing controls! The contents of my selection binding update to match the selected text of the lyric fields.

    Now I can read properties of the selection, such as the selected ranges. I can use this to show suggested rhymes for selected words in the inspector.

    With .searchFocused, you can programmatically drive the focus state of a search field, meaning you can check if a search field is focused on, and programmatically move focus to and from the search field.

    You can now add text suggestions to any text field. I'll use this to provide suggestions for how to finish a line. The suggestions appear as a drop down menu, and when I pick an option my textfield updates with the selected completion.

    SwiftUI also has new graphics capabilities.

    You can now beautifully mix colors together. A new mix modifier on color blends it with another Color by a given amount. We’ve also extended the custom shader feature with the ability to precompile shaders before their first use, so you can avoid frame drops caused by lazy shader compilation.

    There are a bunch of new APIs to give you fine grained control of your scroll views. You can now have deeper level of integration with the state of a ScrollView with .onScrollGeometryChange, which lets you performantly react to changes in things like content offsets, content size and more. Like with this "back to invitation" button, which appears when I scroll past the top of the scroll view's contents.

    You can now detect when a view’s visibility has changed due to scrolling! Letting you create great experiences centered around content moving on or off screen, like this auto-playing video.

    Not only do you have more programatic control over your scroll view, but you also have more scroll positions to programmatically scroll to, like the top edge! There are all sorts of additional knobs for you to turn to really dial in on your perfect scrolling experience like turning off bouncing along a given axis, programmatically stoping scrolling, finely controlling your content alignment, and more. The new Swift 6 language mode enables compile-time data-race safety, and SwiftUI has improved its APIs to make it easier to adopt the new language mode in your apps.

    Views in SwiftUI have always been evaluated on the main actor, and the view protocol is now marked with the main actor annotation to reflect that. That mean all types conforming to View are implicitly isolated to the main actor by default. So if you were explicitly marking your Views as main actor, you can now remove that annotation without any change in behavior. The new Swift 6 language mode is opt-in, so you can take advantage of it whenever you're ready. To learn more about compile time checking, be sure to watch "Migrate your app to Swift 6".

    SwiftUI was designed not just for building brand new apps, but also building new features in existing apps written with UIKit and AppKit. Great interoperability with these frameworks is critical. We made significant improvements to the integration of gestures and animation. You can now take any built-in or custom UIGestureRecognizer and use it in your SwiftUI view hierarchy. This works even on SwiftUI views that aren't directly backed by UIKit.

    And the interoperability improvements go the other way as well. UIKit and AppKit can now take advantage of the power of SwiftUI animations. SwiftUI defines a new animate function on UIView, and NSAnimationContext, allowing UIKit and AppKit changes to be animated using in-process SwiftUI animations.

    Velocity is even automatically preserved for gesture-driven animations, just like in SwiftUI views. And UI and NSViewRepresentable context provide new API for bridging animations started in SwiftUI into UIKit and AppKit, ensuring animations run perfectly in sync, even across framework boundaries.

    For more on using animations across frameworks, be sure to check out "Enhance your UI animations and transitions". Those foundational improvements look great. Now, all that’s left to is practice my karaoke skills, and then I can get together with Sommer and host the party. SwiftUI’s new tools for crafting experiences will help with both building a great practice app, and setting up the event to be a success.

    New APIs for working with volumes, and immersive spaces, as well as new text effects will help bring our karaoke experience to life.

    To practice my beautiful vocals, I’ve built a visionOS practice app, featuring a microphone in a Volume.

    In visionOS 2, volumes can show a baseplate. This helps you get a sense of the bounds of the Volume, and guides you to the window controls, including the new resize handle. Our microphone already has a microphone stand base, so I like the look of it without the system provided baseplate. I'll use the .volumeBaseplateVisibility modifier to disable the system provided baseplate. Nice! I also made my microphone rotate to always face the singer using the new .onVolumeViewpointChange modifier. This is called any time I move to a new side of the Volume, letting me react to changes in how it’s being viewed.

    I’ve got my microphone, now I need a place to put it, and there’s no better place to sing karaoke than a moody karaoke lounge.

    I’ve already got a beautiful immersive space, and I can now control the allowed immersion levels.

    By choosing an initial immersion of fifty percent, and a minimum of forty, I can help singers ease into the karaoke lounge.

    To help set the vibe, I can now apply effects to the passthrough video around my progressive immersive space. I could use preferred-surroundings-effect to dim video passthrough or to really make our karaoke experience special, I can use colorMultiply for some cool mood lighting! Ooooh, that’s fantastic.

    For more on improvements to volumes and immersive spaces, including new ways to attach ornaments and specify supported viewpoints be sure to check out “Dive deep into volumes and immersive spaces”.

    The stage is set but before we start the party, we’ll need lyrics we can follow along with! You can now extend SwiftUI Text views with custom rendering effects and interaction behaviors. I can use this to build a word highlighting effect for our karaoke lyrics. My karaoke renderer creates a copy of the text behind the original drawing, which is blurred. and has a tint, making the text look like it's glowing purple! By applying this highlight effect to only certain words, and adding some final polish, I can make some truly incredible effects, like this karaoke word highlighting. To learn all about how to create these amazing text effects, be sure to check out "Create custom visual effects with SwiftUI." Alright Sommer, I think we’ve done all of our party prep. It really is coming together! These changes to SwiftUI really helped Sommer and I make the most of our apps, and we want you to make the most of yours too. If you have a sidebar-based iPad or tvOS app, leverage the new tab view APIs to allow for more flexibility, and for document based apps, enhance your new document launch experience. Add new windowing and input capabilities to your macOS and visionOS apps. Make the most of your live activity by fine-tuning your experience on watchOS. And take advantage of the new capabilities in volumes and immersive spaces on visionOS. It’s looking like a good year to be a SwiftUI developer! And karaoke singer. Alright Sam, are you ready? I was born ready Sommer. Hey Siri, Start the Party!

    • 1:38 - TabView

      import SwiftUI
      
      struct KaraokeTabView: View {
          @State var customization = TabViewCustomization()
          
          var body: some View {
              TabView {
                  Tab("Parties", image: "party.popper") {
                      PartiesView(parties: Party.all)
                  }
                  .customizationID("karaoke.tab.parties")
                  
                  Tab("Planning", image: "pencil.and.list.clipboard") {
                      PlanningView()
                  }
                  .customizationID("karaoke.tab.planning")
      
                  Tab("Attendance", image: "person.3") {
                      AttendanceView()
                  }
                  .customizationID("karaoke.tab.attendance")
      
                  Tab("Song List", image: "music.note.list") {
                      SongListView()
                  }
                  .customizationID("karaoke.tab.songlist")
              }
              .tabViewStyle(.sidebarAdaptable)
              .tabViewCustomization($customization)
          }
      }
      
      struct PartiesView: View {
          var parties: [Party]
          var body: some View { Text("PartiesView") }
      }
      
      struct PlanningView: View {
          var body: some View { Text("PlanningView") }
      }
      
      struct AttendanceView: View {
          var body: some View { Text("AttendanceView") }
      }
      
      struct SongListView: View {
          var body: some View { Text("SongListView") }
      }
      
      struct Party {
          static var all: [Party] = []
      }
      
      #Preview {
          KaraokeTabView()
      }
    • 2:28 - Presentation sizing

      import SwiftUI
      
      struct AllPartiesView: View {
          @State var showAddSheet: Bool = true
          var parties: [Party] = []
          
          var body: some View {
              PartiesGridView(parties: parties, showAddSheet: $showAddSheet)
                  .sheet(isPresented: $showAddSheet) {
                      AddPartyView()
                          .presentationSizing(.form)
                  }
          }
      }
      
      struct PartiesGridView: View {
          var parties: [Party]
          @Binding var showAddSheet: Bool
          
          var body: some View {
              Text("PartiesGridView")
          }
      }
      
      struct AddPartyView: View {
          var body: some View {
              Text("AddPartyView")
          }
      }
      
      struct Party {
          static var all: [Party] = []
      }
      
      #Preview {
          AllPartiesView()
      }
    • 2:39 - Zoom transition

      import SwiftUI
      
      struct PartyView: View {
          var party: Party
          @Namespace() var namespace
          
          var body: some View {
              NavigationLink {
                  PartyDetailView(party: party)
                      .navigationTransition(.zoom(
                          sourceID: party.id, in: namespace))
              } label: {
                  Text("Party!")
              }
              .matchedTransitionSource(id: party.id, in: namespace)
          }
      }
      
      struct PartyDetailView: View {
          var party: Party
          
          var body: some View {
              Text("PartyDetailView")
          }
      }
      
      struct Party: Identifiable {
          var id = UUID()
          static var all: [Party] = []
      }
      
      #Preview {
          @Previewable var party: Party = Party()
          NavigationStack {
              PartyView(party: party)
          }
      }
    • 3:18 - Controls API

      import WidgetKit
      import SwiftUI
      
      struct StartPartyControl: ControlWidget {
          var body: some ControlWidgetConfiguration {
              StaticControlConfiguration(
                  kind: "com.apple.karaoke_start_party"
              ) {
                  ControlWidgetButton(action: StartPartyIntent()) {
                      Label("Start the Party!", systemImage: "music.mic")
                      Text(PartyManager.shared.nextParty.name)
                  }
              }
          }
      }
      
      // Model code
      
      class PartyManager {
          static let shared = PartyManager()
          var nextParty: Party = Party(name: "WWDC Karaoke")
      }
      
      struct Party {
          var name: String
      }
      
      // AppIntent
      
      import AppIntents
      
      struct StartPartyIntent: AppIntent {
          static let title: LocalizedStringResource = "Start the Party"
          
          func perform() async throws -> some IntentResult {
              return .result()
          }
      }
    • 3:49 - Function plotting

      import SwiftUI
      import Charts
      
      struct AttendanceView: View {
          var body: some View {
              Chart {
                LinePlot(x: "Parties", y: "Guests") { x in
                  pow(x, 2)
                }
                .foregroundStyle(.purple)
              }
              .chartXScale(domain: 1...10)
              .chartYScale(domain: 1...100)
          }
      }
      
      #Preview {
          AttendanceView()
              .padding(40)
      }
    • 4:18 - Dynamic table columns

      import SwiftUI
      
      struct SongCountsTable: View {
      
          var body: some View {
              Table(Self.guestData) {
                  // A static column for the name
                  TableColumn("Name", value: \.name)
                  
                  TableColumnForEach(Self.partyData) { party in
                      TableColumn(party.name) { guest in
                          Text(guest.songsSung[party.id] ?? 0, format: .number)
                      }
                  }
              }
          }
          
          private static func randSongsSung(low: Bool = false) -> [Int : Int] {
              var songs: [Int : Int] = [:]
              for party in partyData {
                  songs[party.id] = low ? Int.random(in: 0...3) : Int.random(in: 3...12)
              }
              return songs
          }
          
          private static let guestData: [GuestData] = [
              GuestData(name: "Sommer", songsSung: randSongsSung()),
              GuestData(name: "Sam", songsSung: randSongsSung()),
              GuestData(name: "Max", songsSung: randSongsSung()),
              GuestData(name: "Kyle", songsSung: randSongsSung(low: true)),
              GuestData(name: "Matt", songsSung: randSongsSung(low: true)),
              GuestData(name: "Apollo", songsSung: randSongsSung()),
              GuestData(name: "Anna", songsSung: randSongsSung()),
              GuestData(name: "Raj", songsSung: randSongsSung()),
              GuestData(name: "John", songsSung: randSongsSung(low: true)),
              GuestData(name: "Harry", songsSung: randSongsSung()),
              GuestData(name: "Luca", songsSung: randSongsSung()),
              GuestData(name: "Curt", songsSung: randSongsSung()),
              GuestData(name: "Betsy", songsSung: randSongsSung())
          ]
          
          private static let partyData: [PartyData] = [
              PartyData(partyNumber: 1, numberGuests: 5),
              PartyData(partyNumber: 2, numberGuests: 6),
              PartyData(partyNumber: 3, numberGuests: 7),
              PartyData(partyNumber: 4, numberGuests: 9),
              PartyData(partyNumber: 5, numberGuests: 9),
              PartyData(partyNumber: 6, numberGuests: 10),
              PartyData(partyNumber: 7, numberGuests: 11),
              PartyData(partyNumber: 8, numberGuests: 12),
              PartyData(partyNumber: 9, numberGuests: 11),
              PartyData(partyNumber: 10, numberGuests: 13),
          ]
          
      }
      
      struct GuestData: Identifiable {
          let name: String
          let songsSung: [Int : Int]
          
          let id = UUID()
      }
      
      struct PartyData: Identifiable {
          let partyNumber: Int
          let numberGuests: Int
          let symbolSize = 100
          
          var id: Int {
              partyNumber
          }
          
          var name: String {
              "\(partyNumber)"
          }
      }
      
      #Preview {
          SongCountsTable()
              .padding(40)
      }
    • 4:42 - Mesh gradients

      import SwiftUI
      
      struct MyMesh: View {
          var body: some View {
              MeshGradient(
                  width: 3,
                  height: 3,
                  points: [
                      .init(0, 0), .init(0.5, 0), .init(1, 0),
                      .init(0, 0.5), .init(0.3, 0.5), .init(1, 0.5),
                      .init(0, 1), .init(0.5, 1), .init(1, 1)
                  ],
                  colors: [
                      .red, .purple, .indigo,
                      .orange, .cyan, .blue,
                      .yellow, .green, .mint
                  ]
              )
          }
      }
      
      #Preview {
          MyMesh()
              .statusBarHidden()
      }
    • 5:14 - Document launch scene

      DocumentGroupLaunchScene("Your Lyrics") {
          NewDocumentButton()
          Button("New Parody from Existing Song") {
              // Do something!
          }
      } background: {
          PinkPurpleGradient()
      } backgroundAccessoryView: { geometry in
          MusicNotesAccessoryView(geometry: geometry)
               .symbolEffect(.wiggle(.rotational.continuous()))
      } overlayAccessoryView: { geometry in
          MicrophoneAccessoryView(geometry: geometry)
      }
    • 7:04 - Window styling and default placement

      Window("Lyric Preview", id: "lyricPreview") {
          LyricPreview()
      }
        .windowStyle(.plain)
        .windowLevel(.floating)
        .defaultWindowPlacement { content, context in
            let displayBounds = context.defaultDisplay.visibleRect
            let contentSize = content.sizeThatFits(.unspecified)
            return topPreviewPlacement(size: contentSize, bounds: displayBounds)
        }
      }
    • 7:30 - Window Drag Gesture

      Text(currentLyric)
          .background(.thinMaterial, in: .capsule)
          .gesture(WindowDragGesture())
    • 8:18 - Push window environment action

      struct EditorView: View {
          @Environment(\.pushWindow) private var pushWindow
          
          var body: some View {
              Button("Play", systemImage: "play.fill") {
                  pushWindow(id: "lyric-preview")
              }
          }
      }
    • 8:47 - Hover effects

      struct ProfileButtonStyle: ButtonStyle {
          func makeBody(configuration: Configuration) -> some View {
              configuration.label
                  .background(.thinMaterial)
                  .hoverEffect(.highlight)
                  .clipShape(.capsule)
                  .hoverEffect { effect, isActive, _ in
                      effect.scaleEffect(isActive ? 1.05 : 1.0)
                  }
          }
      }
    • 9:14 - Modifier key alternates

      Button("Preview Lyrics in Window") {
          // show preview in window
      }
      .modifierKeyAlternate(.option) {
          Button("Preview Lyrics in Full Screen") {
              // show preview in full screen
          }
      }
      .keyboardShortcut("p", modifiers: [.shift, .command])
    • 9:32 - Responding to modifier keys

      var body: some View {
              LyricLine()
                  .overlay(alignment: .top) {
                      if showBouncingBallAlignment {
                          // Show bouncing ball alignment guide
                      }
                  }
                  .onModifierKeysChanged(mask: .option) {
                      showBouncingBallAlignment = !$1.isEmpty
                  }
          }
    • 9:55 - Pointer customization

      ForEach(resizeAnchors) { anchor in
          ResizeHandle(anchor: anchor)
               .pointerStyle(.frameResize(position: anchor.position))
      }
    • 10:23 - Pencil squeeze gesture

      @Environment(\.preferredPencilSqueezeAction) var preferredAction
          
      var body: some View {
          LyricsEditorView()
              .onPencilSqueeze { phase in
                  if preferredAction == .showContextualPalette, case let .ended(value) = phase {
                      if let anchorPoint = value.hoverPose?.anchor {
                          lyricDoodlePaletteAnchor = .point(anchorPoint)
                      }
                      lyricDoodlePalettePresented = true
                  }
             }
    • 13:13 - Custom containers

      struct DisplayBoard<Content: View>: View {
        @ViewBuilder var content: Content
      
        var body: some View {
          DisplayBoardCardLayout {
            ForEach(subviewOf: content) { subview in
              CardView {
                subview
              }
            }
          }
          .background { BoardBackgroundView() }
        }
      }
      
      DisplayBoard {
        Text("Scrolling in the Deep")
        Text("Born to Build & Run")
        Text("Some Body Like View")
      
        ForEach(songsFromSam) { song in
          Text(song.title)
        }
      }
    • 13:35 - Custom containers with sectioning

      DisplayBoard {
        Section("Matt's Favorites") {
          Text("Scrolling in the Deep")
          Text("Born to Build & Run")
          Text("Some Body Like View")
            .displayBoardCardRejected(true)
        }
      	Section("Sam's Favorites") {
          ForEach(songsFromSam) { song in
            Text(song.title)
          }
        }
      }
    • 13:52 - Entry macro

      extension EnvironmentValues {
        @Entry var karaokePartyColor: Color = .purple
      }
      
      extension FocusValues {
        @Entry var lyricNote: String? = nil
      }
      
      extension Transaction {
        @Entry var animatePartyIcons: Bool = false
      }
      
      extension ContainerValues {
        @Entry var displayBoardCardStyle: DisplayBoardCardStyle = .bordered
      }
    • 14:12 - Default accessibility label augmentation

      SongView(song)
        .accessibilityElement(children: .combine)
        .accessibilityLabel { label in
          if let rating = song.rating {
            Text(rating)
          }
          label
        }
    • 14:52 - Previewable

      #Preview {
         @Previewable @State var showAllSongs = true
         Toggle("Show All songs", isOn: $showAllSongs)
      }
    • 15:06 - Programatic text selection

      struct LyricView: View {
        @State private var selection: TextSelection?
        
        var body: some View {
          TextField("Line \(line.number)", text: $line.text, selection: $selection)
          // ...
        }
      }
    • 15:19 - Getting selected ranges

      InspectorContent(text: line.text, ranges: selection?.ranges)
    • 15:29 - Binding to search field focus state

      // Binding to search field focus state
      
      struct SongSearchView: View {
        @FocusState private var isSearchFieldFocused: Bool
        
        @State private var searchText = ""
        @State private var isPresented = false
      
        var body: some View {
          NavigationSplitView {
            Text("Power Ballads")
            Text("Show Tunes")
          } detail: {
            // ...
            if !isSearchFieldFocused {
              Button("Find another song") {
                isSearchFieldFocused = true
              }
            }
          }
          .searchable(text: $searchText, isPresented: $isPresented)
          .searchFocused($isSearchFieldFocused)
        }
      }
    • 15:41 - Text suggestions

      TextField("Line \(line.number)", text: $line.text)
        .textInputSuggestions {
          ForEach(lyricCompletions) {
            Text($0.attributedCompletion)
              .textInputCompletion($0.text)
          }
        }
    • 15:59 - Color mixing

      Color.red.mix(with: .purple, by: 0.2)
      Color.red.mix(with: .purple, by: 0.5)
      Color.red.mix(with: .purple, by: 0.8)
    • 16:13 - Custom shaders

      ContentView()
        .task {
          let slimShader = ShaderLibrary.slim()
          try! await slimShader.compile(as: .layerEffect)
        }
    • 16:23 - React to scroll geometry changes

      struct ContentView: View {
        @State private var showBackButton = false
      
        ScrollView {
          // ...
        }
        .onScrollGeometryChange(for: Bool.self) { geometry in
          geometry.contentOffset.y < geometry.contentInsets.top
        } action: { wasScrolledToTop, isScrolledToTop in
          withAnimation {
            showBackButton = !isScrolledToTop
          }
        }
      }
    • 16:42 - React to scroll visibility changes

      struct AutoPlayingVideo: View {
        @State private var player: AVPlayer = makePlayer()
      
        var body: some View {
          VideoPlayer(player: player)
            .onScrollVisibilityChange(threshold: 0.2) { visible in
              if visible {
                player.play()
              } else {
                player.pause()
              }
            }
        }
      }
    • 16:54 - New scroll positions

      struct ContentView: View {
        @State private var position: ScrollPosition =
          .init(idType: Int.self)
      
        var body: some View {
          ScrollView {
            // ... 
          }
          .scrollPosition($position)
          .overlay {
            FloatingButton("Back to Invitation") {
              position.scrollTo(edge: .top)
            }
          }
        }
      }
    • 18:17 - Gesture interoperability

      struct VideoThumbnailScrubGesture: UIGestureRecognizerRepresentable {
        @Binding var progress: Double
      
        func makeUIGestureRecognizer(context: Context) -> VideoThumbnailScrubGestureRecognizer {
          VideoThumbnailScrubGestureRecognizer()
        }
      
        func handleUIGestureRecognizerAction(
          _ recognizer: VideoThumbnailScrubGestureRecognizer, context: Context
        ) {
          progress = recognizer.progress
        }
      }
      
      struct VideoThumbnailTile: View {
        var body: some View {
          VideoThumbnail()
            .gesture(VideoThumbnailScrubGesture(progress: $progress))
        }
      }
    • 18:34 - SwiftUI animations in UIKit and AppKit

      let animation = SwiftUI.Animation.spring(duration: 0.8)
      
      // UIKit
      UIView.animate(animation) {
          view.center = endOfBracelet
      }
      
      // AppKit
      NSAnimationContext.animate(animation) {
          view.center = endOfBracelet
      }
    • 18:57 - Representable animation bridging

      struct BeadBoxWrapper: UIViewRepresentable {
        @Binding var isOpen: Bool
      
        func updateUIView(_ box: BeadBox, context: Context) {
      		context.animate {
      		  box.lid.center.y = isOpen ? -100 : 100
      		}
        }
      }
    • 19:59 - Volume baseplate visibility

      struct KaraokePracticeApp: App {
        var body: some Scene {
          WindowGroup {
            ContentView()
          }
          .windowStyle(.volumetric)
          .defaultWorldScaling(.trueScale)
          .volumeBaseplateVisibility(.hidden)
        }
      }
    • 20:15 - React to volume viewpoint changes

      struct MicrophoneView: View {
       @State var micRotation: Rotation3D = .identity
          
        var body: some View {
          Model3D(named: "microphone")
            .onVolumeViewpointChange { _, new in
              micRotation = rotateToFace(new)
            }
            .rotation3DEffect(micRotation)
            .animation(.easeInOut, value: micRotation)
        } 
      }
    • 20:38 - Control allowed immersion levels

      struct KaraokeApp: App {
        @State private var immersion: ImmersionStyle = .progressive(
          0.4...1.0, initialAmount: 0.5)
        
        var body: some Scene {
          ImmersiveSpace(id: "Karaoke") {
            LoungeView()
          }
          .immersionStyle(selection: $immersion, in: immersion)
         }
      }
    • 21:00 - Preferred surrounding effects

      struct LoungeView: View {
        var body: some View {
          StageView()
            .preferredSurroundingsEffect(.colorMultiply(.purple))
        }
      }
    • 21:33 - Custom text renderers

      struct KaraokeRenderer: TextRenderer {
        func draw(
          layout: Text.Layout,
          in context: inout GraphicsContext
        ) {
          for line in layout {
            for run in line {
              var glow = context
      
              glow.addFilter(.blur(radius: 8))
              glow.addFilter(purpleColorFilter)
      
              glow.draw(run)
              context.draw(run)
            }
          }
        }
      }
      
      struct LyricsView: View {
        var body: some View {
          Text("A Whole View World")
            .textRenderer(KaraokeRenderer())
        }
      }
      
      #Preview {
        LyricsView()
      }
  • Looking for something specific? Enter a topic above and jump straight to the good stuff.

    An error occurred when submitting your query. Please check your Internet connection and try again.

Developer Footer

  • Videos
  • WWDC24
  • What’s new in SwiftUI
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Extensions
    • App Store
    • Audio & Video
    • Augmented Reality
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • 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
    • 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
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines