Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2025 Privacy & SecuritySwiftUI & UI Frameworks

WWDC25 · 26 min · Privacy & Security / SwiftUI & UI Frameworks

What’s new in UIKit

Modernize your app with the latest APIs in UIKit, including enhanced menu bar support, automatic observation tracking, a new UI update method, and improvements to animations. We’ll also cover how you can include SwiftUI scenes in your UIKit app and explore SF Symbols, HDR color pickers, and more.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 18 snippets

Main menu system configuration swift · at 4:56 ↗
// Main menu system configuration

var config = UIMainMenuSystem.Configuration()

// Declare support for default commands, like printing
config.printingPreference = .included

// Opt out of default commands, like inspector
config.inspectorPreference = .removed

// Configure the Find commands to be a single "Search" element
config.findingConfiguration.style = .search
Main menu system build configuration swift · at 5:39 ↗
// Main menu system configuration

// Have the main menu system build using this configuration, and make custom additions.
// Call this early, e.g. in application(_:didFinishLaunchingWithOptions:), and call it once
UIMainMenuSystem.shared.setBuildConfiguration(config) { builder in
    builder.insertElements([...], afterCommand: #selector(copy(_:)))

    let deleteKeyCommand = UIKeyCommand(...)
    builder.replace(command: #selector(delete(_:)), withElements: [deleteKeyCommand])
}
Keyboard shortcut repeatability swift · at 7:01 ↗
// Keyboard shortcut repeatability

let keyCommand = UIKeyCommand(...)
keyCommand.repeatBehavior = .nonRepeatable
Focus-based deferred menu elements (App Delegate) swift · at 7:43 ↗
// Focus-based deferred menu elements

extension UIDeferredMenuElement.Identifier {
    static let browserHistory: Self = .init(rawValue: "com.example.deferred-element.history")
}

// Create a focus-based deferred element that will display browser history
let historyDeferredElement = UIDeferredMenuElement.usingFocus(
    identifier: .browserHistory,
    shouldCacheItems: false
)

// Insert it into the app’s custom History menu when building the main menu
builder.insertElements([historyDeferredElement], atEndOfMenu: .history)
Focus-based deferred menu elements (View Controller) swift · at 8:06 ↗
// Focus-based deferred menu elements

class BrowserViewController: UIViewController {

    // ...
  
    override func provider(
        for deferredElement: UIDeferredMenuElement
    ) -> UIDeferredMenuElement.Provider? {
        if deferredElement.identifier == .browserHistory {
            return UIDeferredMenuElement.Provider { completion in
                let browserHistoryMenuElements = profile.browserHistoryElements()
                completion(browserHistoryMenuElements)
            }
        }
        return nil
    }
}
Using an Observable object and automatic observation tracking swift · at 10:54 ↗
// Using an Observable object and automatic observation tracking

@Observable class UnreadMessagesModel {
    var showStatus: Bool
    var statusText: String
}

class MessageListViewController: UIViewController {
    var unreadMessagesModel: UnreadMessagesModel

    var statusLabel: UILabel
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        statusLabel.alpha = unreadMessagesModel.showStatus ? 1.0 : 0.0
        statusLabel.text = unreadMessagesModel.statusText
    }
}
Configuring a UICollectionView cell with automatic observation tracking swift · at 11:48 ↗
// Configuring a UICollectionView cell with automatic observation tracking

@Observable class ListItemModel {
    var icon: UIImage
    var title: String
    var subtitle: String
}

func collectionView(
    _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
    let listItemModel = listItemModel(for: indexPath)
    cell.configurationUpdateHandler = { cell, state in
        var content = UIListContentConfiguration.subtitleCell()
        content.image = listItemModel.icon
        content.text = listItemModel.title
        content.secondaryText = listItemModel.subtitle
        cell.contentConfiguration = content
    }
    return cell
}
Using automatic observation tracking and updateProperties() swift · at 13:27 ↗
// Using automatic observation tracking and updateProperties()

@Observable class BadgeModel {
   var badgeCount: Int?
}

class MyViewController: UIViewController {
   var model: BadgeModel
   let folderButton: UIBarButtonItem

    override func updateProperties() {
        super.updateProperties()

        if let badgeCount = model.badgeCount {
            folderButton.badge = .count(badgeCount)
        } else {
            folderButton.badge = nil
        }
   }
}
Using the flushUpdates animation option to automatically animate updates swift · at 16:57 ↗
// Using the flushUpdates animation option to automatically animate updates

// Automatically animate changes with Observable objects
UIView.animate(options: .flushUpdates) {
    model.badgeColor = .red
}
Automatically animate changes to Auto Layout constraints with flushUpdates swift · at 17:23 ↗
// Automatically animate changes to Auto Layout constraints
UIView.animate(options: .flushUpdates) {
    // Change the constant of a NSLayoutConstraint
    topSpacingConstraint.constant = 20
    
    // Change which constraints are active
    leadingEdgeConstraint.isActive = false
    trailingEdgeConstraint.isActive = true
}
Setting up a UIHostingSceneDelegate swift · at 18:07 ↗
// Setting up a UIHostingSceneDelegate

import UIKit
import SwiftUI

class ZenGardenSceneDelegate: UIResponder, UIHostingSceneDelegate {
    static var rootScene: some Scene {
        WindowGroup(id: "zengarden") {
            ZenGardenView()
        }

        #if os(visionOS)
        ImmersiveSpace(id: "zengardenspace") {
            ZenGardenSpace()
        }
        .immersionStyle(selection: .constant(.full),
                        in: .mixed, .progressive, .full)
        #endif 
    }
}
Using a UIHostingSceneDelegate swift · at 18:28 ↗
// Using a UIHostingSceneDelegate 

func application(_ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions) -> UISceneConfiguration {

    let configuration = UISceneConfiguration(name: "Zen Garden Scene",
                                             sessionRole: connectingSceneSession.role)

    configuration.delegateClass = ZenGardenSceneDelegate.self
    return configuration
}
Requesting a scene swift · at 18:41 ↗
// Requesting a scene

func openZenGardenSpace() {
    let request = UISceneSessionActivationRequest(
        hostingDelegateClass: ZenGardenSceneDelegate.self,
        id: “zengardenspace")!
  
    UIApplication.shared.activateSceneSession(for: request)
}
HDR color support swift · at 19:18 ↗
// Create an HDR red relative to a 2.5x peak white
let hdrRed = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0, linearExposure: 2.5)
HDR color picking swift · at 19:50 ↗
// Support picking HDR colors relative to a 
// maximum peak white of 2x
colorPickerController.maximumLinearExposure = 2.0
Mixing SDR and HDR content swift · at 20:06 ↗
// Mixing SDR and HDR content

registerForTraitChanges([UITraitHDRHeadroomUsageLimit.self]) { traitEnvironment, previousTraitCollection in
    let currentHeadroomLimit = traitEnvironment.traitCollection.hdrHeadroomUsageLimit
    // Update HDR usage based on currentHeadroomLimit’s value
}
Adopting Swift notifications swift · at 20:54 ↗
// Adopting Swift notifications

override func viewDidLoad() {
    super.viewDidLoad()

    let keyboardObserver = NotificationCenter.default.addObserver(
        of: UIScreen.self
        for: .keyboardWillShow
    ) { message in
        UIView.animate(
            withDuration: message.animationDuration, delay: 0, options: .flushUpdates
        ) {
            // Use message.endFrame to animate the layout of views with the keyboard
            let keyboardOverlap = view.bounds.maxY - message.endFrame.minY
            bottomConstraint.constant = keyboardOverlap
        }
    }
}
Using a symbol content transition to automatically animate symbol updates swift · at 24:26 ↗
// Using a symbol content transition to automatically animate symbol updates

var configuration = UIButton.Configuration.plain()
configuration.symbolContentTransition = UISymbolContentTransition(.replace)

Resources