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

2025 SwiftUI & UI Frameworks

WWDC25 · 17 min · SwiftUI & UI Frameworks

Make your UIKit app more flexible

Find out how your UIKit app can become more flexible on iPhone, iPad, Mac, and Apple Vision Pro by using scenes and container view controllers. Learn to unlock your app’s full potential by transitioning from an app-centric to a scene-based lifecycle, including enhanced window resizing and improved multitasking. Explore enhancements to UISplitViewController, such as interactive column resizing and first-class support for inspector columns. And make your views and controls more adaptive by adopting new layout APIs.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 15 snippets

Specify the scene configuration swift · at 3:02 ↗
// Specify the scene configuration

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

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

        if sceneSession.role == .windowExternalDisplayNonInteractive {
            return UISceneConfiguration(name: "Timer Scene",
                                        sessionRole: sceneSession.role)
        } else {
            return UISceneConfiguration(name: "Main Scene",
                                        sessionRole: sceneSession.role)
        }
    }
}
Configure the UI swift · at 3:30 ↗
// Configure the UI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var timerModel = TimerModel()

    func scene(_ scene: UIScene,
               willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {

        let windowScene = scene as! UIWindowScene
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = TimerViewController(model: timerModel)
        window.makeKeyAndVisible()
        self.window = window
    }
}
Handle life cycle events swift · at 3:56 ↗
// Handle life cycle events

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var timerModel = TimerModel()

    // ...

    func sceneDidEnterBackground(_ scene: UIScene) {
        timerModel.pause()
    }
}
Restore UI state swift · at 4:09 ↗
// Restore UI state

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var timerModel = TimerModel()

    // ...

    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
        let userActivity = NSUserActivity(activityType: "com.example.timer.ui-state")
        userActivity.userInfo = ["selectedTimeFormat": timerModel.selectedTimeFormat]
        return userActivity
    }

    func scene(_ scene: UIScene restoreInteractionStateWith userActivity: NSUserActivity) {
        if let selectedTimeFormat = userActivity?["selectedTimeFormat"] as? String {
            timerModel.selectedTimeFormat = selectedTimeFormat
        }
    
}
Adapt for the split view controller layout environment swift · at 4:46 ↗
// Adapt for the split view controller layout environment

override func updateConfiguration(using state: UICellConfigurationState) {
   
    // ...
    
    if state.traitCollection.splitViewControllerLayoutEnvironment == .collapsed {
        accessories = [.disclosureIndicator()]
    } else {
        accessories = []
    }
}
Customize the minimum, maximum, and preferred column widths swift · at 6:11 ↗
// Customize the minimum, maximum, and preferred column widths

let splitViewController = // ...

splitViewController.minimumPrimaryColumnWidth = 200.0
splitViewController.maximumPrimaryColumnWidth = 400.0
splitViewController.preferredSupplementaryColumnWidth = 500.0
Show an inspector column swift · at 7:37 ↗
// Show an inspector column

let splitViewController = // ... 
splitViewController.setViewController(inspectorViewController, for: .inspector)

splitViewController.show(.inspector)
Managing tab groups swift · at 9:19 ↗
// Managing tab groups

let group = UITabGroup(title: "Library", ...)
group.managingNavigationController = UINavigationController()

// ...

// MARK: - UITabBarControllerDelegate

func tabBarController(
    _ tabBarController: UITabBarController,
    displayedViewControllersFor tab: UITab,
    proposedViewControllers: [UIViewController]) -> [UIViewController] {

    if tab.identifier == "Library" && !self.allowsSelectingLibraryTab {
        return []
    } else {
        return proposedViewControllers
    }
}
Preferred minimum size swift · at 10:25 ↗
// Specify a preferred minimum size

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    func scene(_ scene: UIScene,
               willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {

        let windowScene = scene as! UIWindowScene
        windowScene.sizeRestrictions?.minimumSize.width = 500.0
    }
}
Position content using the layout margins guide swift · at 11:57 ↗
// Position content using the layout margins guide

let containerView = // ...
let contentView = // ...

let contentGuide = containerView.layoutMarginsGuide

NSLayoutConstraint.activate([
    contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
    contentView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
    contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor)
    contentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor)
])
Specify the window control style swift · at 12:34 ↗
// Specify the window control style

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    func preferredWindowingControlStyle(
        for scene: UIWindowScene) -> UIWindowScene.WindowingControlStyle {
        return .unified
    }
}
Respect the window control area swift · at 13:04 ↗
// Respect the window control area

let containerView = // ...
let contentView = // ...

let contentGuide = containerView.layoutGuide(for: .margins(cornerAdaptation: .horizontal)

NSLayoutConstraint.activate([
    contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
    contentView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
    contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor),
    contentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor)
])
Request orientation lock swift · at 13:57 ↗
// Request orientation lock

class RaceViewController: UIViewController {

    override var prefersInterfaceOrientationLocked: Bool {
        return isDriving
    }

    // ...

    var isDriving: Bool = false {
        didSet {
            if isDriving != oldValue {
                setNeedsUpdateOfPrefersInterfaceOrientationLocked()
            }
        }
    }
}
Observe the interface orientation lock swift · at 14:18 ↗
// Observe the interface orientation lock

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var game = Game()

    func windowScene(
        _ windowScene: UIWindowScene,
        didUpdateEffectiveGeometry previousGeometry: UIWindowScene.Geometry) {
        
        let wasLocked = previousGeometry.isInterfaceOrientationLocked
        let isLocked = windowScene.effectiveGeometry.isInterfaceOrientationLocked

        if wasLocked != isLocked {
    game.pauseIfNeeded(isInterfaceOrientationLocked: isLocked)
        }
    }
}
Query whether the scene is resizing swift · at 14:44 ↗
// Query whether the scene is resizing

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var gameAssetManager = GameAssetManager()
    var previousSceneSize = CGSize.zero

    func windowScene(
        _ windowScene: UIWindowScene,
        didUpdateEffectiveGeometry previousGeometry: UIWindowScene.Geometry) {

        let geometry = windowScene.effectiveGeometry
        let sceneSize = geometry.coordinateSpace.bounds.size

        if !geometry.isInteractivelyResizing && sceneSize != previousSceneSize {
            previousSceneSize = sceneSize
            gameAssetManager.updateAssets(sceneSize: sceneSize)
        }
    }
}

Resources