View in English

  • 打开菜单 关闭菜单
  • Apple Developer
搜索
关闭搜索
  • Apple Developer
  • 新闻
  • 探索
  • 设计
  • 开发
  • 分发
  • 支持
  • 账户
在“”范围内搜索。

快捷链接

5 快捷链接

视频

打开菜单 关闭菜单
  • 专题
  • 相关主题
  • 所有视频
  • 关于

返回 WWDC25

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 让你的 UIKit App 更加灵活

    探究如何使用场景和容器视图控制器让你的 UIKit App 在 iPhone、iPad、Mac 和 Apple Vision Pro 上变得更加灵活。了解如何从以 App 为中心的生命周期过渡到基于场景的生命周期 (包括优化的窗口大小调整和改进的多任务处理),以便解锁 App 的全部潜能。探索 UISplitViewController 的增强功能,例如以交互方式调整列大小以及为检查器列提供一流支持;并通过采用新的布局 API 来增强视图和控制器的自适应性。

    章节

    • 0:00 - 简介
    • 0:58 - 场景
    • 4:58 - 容器视图控制器
    • 10:45 - 自适应性
    • 15:39 - 未来兼容性
    • 16:07 - 后续步骤

    资源

    • TN3187: Migrating to the UIKit scene-based life cycle
    • UIKit updates
      • 高清视频
      • 标清视频

    相关视频

    WWDC25

    • UIKit 的新功能

    WWDC24

    • 提升 iPadOS 中的标签页和边栏使用体验
  • 搜索此视频…

    大家好 欢迎观看 “让你的 UIKit App 更加灵活” 我叫 Alexander MacLeod 是 UIKit 团队的一名工程师 灵活的 App 能在不同平台上以各种大小 提供令人惊叹的使用体验 它能在任何大小下 提供熟悉且直观的导航体验 在这个视频中 我将介绍一些最佳实践 以确保你的 App 具有灵活性 首先 我将讲解场景的基本概念 并分享它们如何成为 构建灵活应用程序的基础 接下来我将介绍容器视图控制器 如 UISplitViewController 和 UITabBarController 并探索它们如何 为你的 App 带来灵活性 最后我将谈谈 API 以支持你构建自适应 而且真正灵活的 UI 先来聊聊场景 场景是 App UI 的一个实例 它包含 App 的视图控制器和视图 场景提供了 用于处理外部数据的挂钩 例如用于深度链接到 App UI 中某个部分的 URL

    每个场景独立保存和还原 UI 状态 场景决定了 将当前状态持久化到磁盘之前 请求当前状态的最佳机会 你可以在场景重新连接时 查询之前的 UI 状态 这样你就能将场景 恢复到之前的状态 场景还会提供 有关 App 如何显示的背景信息 包括有关屏幕 和窗口几何形状的详细信息 你可以拥有多个场景 每个都有各自的生命周期和状态 专用场景类型旨在封装不同的体验 例如 一款信息 App 可以 提供用于发送新消息的 专用撰写场景 在 iOS 26 上 现可在一个 App 中 混用 SwiftUI 和 UIKit 场景类型 请观看“UIKit 的新功能”讲座 以了解详情 场景提供的可移植性 为灵活 App 奠定了完美的基础

    由于场景对于确保灵活性至关重要 因此不久将 强制采用 UIScene 生命周期 在 iOS 26 之后的下一个主要版本中 在用最新 SDK 进行构建时 必须采用 UIScene 生命周期

    虽然我们鼓励支持多个场景 但唯一强制要求的是 采用场景生命周期 有关如何采用 UIScene 生命周期的 详细信息 请参阅技术说明: “迁移到基于 UIKit 场景的生命周期” 由于场景非常重要 我将为你展示一个实际应用示例 我开发了一个 App 来跟踪 我在特定任务上花费的时间 它有一个功能 可以通过隔空播放 将当前任务传输到 Apple TV App 委托的责任是 为正在连接的场景会话 确定对应的场景配置 在 configurationForConnecing SceneSession 委托模式中 我查看了场景会话的角色 如果角色是非交互式的外接显示屏 我会返回一个定制的场景配置 否则 则首选主场景配置 每个配置都在 App 的 Info.plist 文件中定义

    UISceneDelegate 管理 单个场景的生命周期 在 sceneWillConnectToSession 中 我先创建一个窗口 并将它与 正在连接的场景相关联 请注意 如果场景配置指定了 Storyboard 窗口创建会自动完成

    我指定了窗口的根视图控制器 并向它提供特定于场景的数据 例如计时器模型 对于我的 App 当场景移动到后台时 暂停计时器非常重要 为此 我实现了 sceneDidEnterBackground 委托方法 并暂停计时器 我处理了状态恢复 以确保连接场景的 UI 状态 与之前的状态完全相同 我的场景委托提供了 一个状态恢复活动 其中可以包括选项、导航路径 和其他 UI 状态 系统会保留这个 UI 状态 并将它与场景实例相关联 如果场景稍后重新连接 则状态恢复活动将在 restoreInteractionStateWith userActivity 委托方法中提供 通过使用来自用户活动的信息 填充计时器模型 我可以确保连接场景的 UI 状态 与之前的状态完全相同

    通过采用 UIScene 生命周期 我为灵活应用程序奠定了坚实的基础 现在我将介绍容器视图控制器 并解释它们对于 构建灵活应用程序的重要性 容器视图控制器负责管理 一个或多个子视图控制器的布局 UIKit 提供了多个容器视图控制器 它们都被设计得非常灵活 首先我会谈谈 UISplitViewController UISplitViewController 负责管理 多个相邻内容列的显示 支持在整个信息层次结构中无缝导航 当水平空间有限时 拆分视图控制器会通过 将列折叠到导航堆栈中来进行调整 UISplitViewController 新增了许多功能 首先是交互式列大小调整 你现在可通过拖动拆分视图控制器的 分隔符来调整列的大小 使用指针时 它的形状将进行调整 以指示可以调整列大小的方向 UISplitViewController 提供了每个列 默认的最小、最大和首选宽度 你的 App 中有些列 可能需要以更大宽度显示内容 或只需要默认宽度的一小部分 即可保持正常工作 你可以使用每列相关的 拆分视图控制器属性 自定每列的最小、最大和首选宽度 请注意不要规定一个 会限制可显示列数的宽度 因为这会降低 App 的灵活性 你的 UI 可能需要 依据拆分视图控制器 是展开还是折叠进行调整 在“邮件”中 当拆分视图控制器 处于折叠状态时会显示详情指示符 以表示通过选择单元格 可显示更多内容 名为 splitViewControllerLayoutenvironment 的新特征 表明了父级拆分视图控制器 是展开还是折叠 在这个示例中 查询这个特征可在满足 拆分视图控制器折叠这一条件时 显示详情指示符 此外 还新增了 对检查器列的一流支持 检查器是拆分视图控制器中的一列 用于提供所选内容的更多详细信息 “预览”使用检查器在辅助列中 显示照片旁边的元数据 当拆分浏览控制器展开时 检查器列位于后边缘 与辅助列相邻

    当拆分视图控制器折叠时 它会自动调整 并将检查器列显示为表单

    要在拆分视图控制器中加入检查器 应当为检查器列指定视图控制器 当拆分视图控制器首次出现时 检查器列处于隐藏状态 调用 show 以显示检查器列 UISplitViewController 旨在实现灵活性 可确保你的 App 在任何大小下 都能提供最佳的导航体验 另一个可供你使用的容器是 UITabBarController UITabBarController 在同一区域中 显示多个互斥的内容面板 标签页栏可实现 不同标签页的快速切换 同时在每个面板中 保留每个标签页的当前状态 更重要的是 标签页栏的外观会根据平台进行调整

    在 iPhone 上 标签页栏 位于场景底部 在 Mac 上 标签页栏可位于工具栏中 也可以显示为边栏

    在 Apple Vision Pro 上 标签页栏显示在 场景前缘的装饰元素中 在 iPad 上 标签页栏位于场景顶部 与导航控件并排显示 标签页栏也可以调整为边栏 以便快速访问内容集合 标签页组在边栏中显示其他目标位置 例如 在 iPad 上的“Music”App 中 “资料库”标签页组包括“艺人”、 “专辑”等

    如果边栏不可用 “资料库” 标签页组是标签页目标位置

    UITabBarController 提供 API 来无缝管理这种适应性 首先为标签页组提供管理导航控制器 当标签页组的叶标签页被选中时 它的视图控制器 以及它的父级组的视图控制器 都会被推送到这个导航堆栈上 要自定推送到这个导航栈上的 视图控制器 实施 UITabBarController 委托方法 displayedViewControllersFor tab 在这个示例中 如果无法选择 “资料库”标签页 委托方法会返回一个空数组 从堆栈中省略 “资料库”标签页的视图控制器

    要进一步了解 UITabBarController 如何提供 显示标签页栏或边栏的灵活性 请观看 WWDC24 的“提升 iPadOS 中的标签页栏和边栏使用体验” 采用容器视图控制器 如 UISplitViewController 和 UITabBarController 是确保 App 具有灵活性的最佳方式 虽然这些容器旨在支持各种大小 但你的 App 可能要达到最小尺寸 才能保持核心功能 你可以使用 UISceneSizeRestrictions API 来表示场景内容的首选最小尺寸 指定最小尺寸的最佳时间是 场景即将连接时 在这个示例中 我将首选的最小宽度指定为 500 点 要使你的 App 真正具有灵活性 你自己的 UI 应该能够调整 接下来 我将讨论 支持你构建自适应 UI 的 API 使 UI 具有自适应性的关键步骤 是确保内容保持在安全区域内 安全区域是视图中 适用于交互式内容或重要内容的区域 放置在这个区域之外的内容 很容易被覆盖 例如被导航栏或工具栏覆盖

    内容还可能被状态栏等系统 UI 甚至灵动岛等设备功能遮挡

    边栏在拆分视图控制器的相邻列中 添加了一个非对称的安全区域插入 背景可以在边栏下方 自由延伸到安全区域之外

    信息脚本等内容位于安全区域内 以确保它保持可见 消息气泡使用布局外边距 从安全区域的边缘插入 这样可以保持一致的间距 并与边栏保持清晰的视觉分离

    每个视图都提供了布局参考线 以在内容周围应用标准外边距 布局外边距默认从安全区域插入 在这个示例中 我请求了布局参考线 用于在容器视图中规定内容的位置 然后我使用这个布局参考线 为内容视图配置约束

    在 iPadOS 26 中 场景获得了关闭、 最小化和排列窗口的新控件 类似于 macOS 窗口控件显示在场景中的内容旁边

    场景可以指定首选的 窗口控件样式来搭配内容 若要指定首选样式 请实现 UIWindowSceneDelegate 方法 preferredWindowingControlStyle for scene

    UINavigationBar 等系统组件 会通过在窗口控件周围 排列它们的子视图来自动调整 你的 UI 也应该可根据窗口控件 自行调整 不论控件是什么样式

    为了确保你的 UI 不被遮挡 使用考虑窗口控件的布局参考线 在这个示例中 我请求一个带有 水平角适应的布局外边距参考线 这个布局参考线非常适合用于 场景顶部的条形内容 这些内容应从窗口控件的后边缘插入 然后我使用这个布局参考线 为内容视图配置约束 当 UI 可自适应 界面方向应该是多余的 场景大小调整、设备旋转 和窗口布局更改 最终都会导致场景大小的修改 某些类别的 App 可能会受益于 临时锁定方向 例如 当驾驶游戏需要旋转设备 来操纵车辆方向时 可能需要锁定方向

    当视图控制器可见时 它可以首选锁定的界面方向 若要指定首选方向 请覆盖视图控制器子类中的 prefersInterfaceOrientationLocked 每当这个首选设置发生变化时 调用 setNeedsUpdateOfPrefersInterfaceOrientationLocked

    要观察界面方向锁定 实现 UIWindowSceneDelegate 方法 didUpdateEffectiveGeometry 然后比较 isInterfaceOrientationLocked 的值是否已更改 为了让 App 真正具有自适应性 它应该对大小调整做出快速响应 你的 App UI 中某些元素的绘制 可能需要耗费大量的计算资源

    这在游戏中很常见 当场景改变大小时 许多资源可能需要重新调整大小 在调整大小交互中 为每个大小 重新渲染资源是不必要的 在这个示例中 查询 isInteractivelyResizing 以仅在交互完成后 更新新场景资源的大小

    通过灵活的 App 用户可以随心使用设备 它们为各种大小提供出色的体验 允许它们用于任何方向或布局 UIRequiresFullscreen Info.plist 键 是 iOS 9 中的一种兼容模式 可以防止场景调整大小 UIRequiresFullscreen 已弃用 并会在将来的版本中忽略 具有自适应性的 App 不需要这个键 应将它移除

    还有另一种兼容模式 专门用于新硬件 以前 当发布 具有不同屏幕尺寸的新硬件时 系统对 App 的 UI 进行缩放 或采用信箱模式 这种缩放方式将保持不变 直到你使用更新的 SDK 进行构建 并重新提交你的 App 使用 iOS 26 SDK 进行构建并提交后 系统将不再对 App UI 进行缩放 或采用信箱模式来实现新的屏幕尺寸

    以上就是确保 App 灵活的最佳实践 接下来该怎么做? 在 App 中采用场景生命周期 确保为灵活应用程序奠定坚实的基础 使用容器视图控制器管理 UI 的组件 最后 利用布局参考线等 API 来支持你构建自适应 UI 我迫不及待地想看到 大家的 App 变得更加灵活 谢谢!

    • 3:02 - Specify the scene configuration

      // 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)
              }
          }
      }
    • 3:30 - Configure the UI

      // 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
          }
      }
    • 3:56 - Handle life cycle events

      // Handle life cycle events
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
          var window: UIWindow?
          var timerModel = TimerModel()
      
          // ...
      
          func sceneDidEnterBackground(_ scene: UIScene) {
              timerModel.pause()
          }
      }
    • 4:09 - Restore UI state

      // 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
              }
          
      }
    • 4:46 - Adapt for the split view controller layout environment

      // Adapt for the split view controller layout environment
      
      override func updateConfiguration(using state: UICellConfigurationState) {
         
          // ...
          
          if state.traitCollection.splitViewControllerLayoutEnvironment == .collapsed {
              accessories = [.disclosureIndicator()]
          } else {
              accessories = []
          }
      }
    • 6:11 - Customize the minimum, maximum, and preferred column widths

      // Customize the minimum, maximum, and preferred column widths
      
      let splitViewController = // ...
      
      splitViewController.minimumPrimaryColumnWidth = 200.0
      splitViewController.maximumPrimaryColumnWidth = 400.0
      splitViewController.preferredSupplementaryColumnWidth = 500.0
    • 7:37 - Show an inspector column

      // Show an inspector column
      
      let splitViewController = // ... 
      splitViewController.setViewController(inspectorViewController, for: .inspector)
      
      splitViewController.show(.inspector)
    • 9:19 - Managing tab groups

      // 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
          }
      }
    • 10:25 - Preferred minimum size

      // 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
          }
      }
    • 11:57 - Position content using the layout margins guide

      // 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)
      ])
    • 12:34 - Specify the window control style

      // Specify the window control style
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      
          func preferredWindowingControlStyle(
              for scene: UIWindowScene) -> UIWindowScene.WindowingControlStyle {
              return .unified
          }
      }
    • 13:04 - Respect the window control area

      // 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)
      ])
    • 13:57 - Request orientation lock

      // Request orientation lock
      
      class RaceViewController: UIViewController {
      
          override var prefersInterfaceOrientationLocked: Bool {
              return isDriving
          }
      
          // ...
      
          var isDriving: Bool = false {
              didSet {
                  if isDriving != oldValue {
                      setNeedsUpdateOfPrefersInterfaceOrientationLocked()
                  }
              }
          }
      }
    • 14:18 - Observe the interface orientation lock

      // 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)
              }
          }
      }
    • 14:44 - Query whether the scene is resizing

      // 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)
              }
          }
      }
    • 0:00 - 简介
    • 使用场景、容器视图控制器和其他 API,让 UIKit App 能够灵活适配不同屏幕尺寸和平台。

    • 0:58 - 场景
    • 场景代表 App UI 的不同实例。每个场景都能独立管理自己的状态,并在重新连接时无缝恢复之前的使用状态。场景会提供有关 App 显示的上下文,例如屏幕尺寸和窗口几何形状。 从 iOS 26 之后的下一个主要版本开始,必须采用 UIScene 生命周期。

    • 4:58 - 容器视图控制器
    • “UISplitViewController”和“UITabBarController”等容器视图控制器会管理一个或多个子视图控制器的布局,赋予了 App 灵活性、适配性和可定制性。你可使用 UISceneRestrictions API 来表示 App 中各个场景的最小尺寸。

    • 10:45 - 自适应性
    • 布局参考线和外边距有助于将 App 内容始终放置在设备的安全区域内。 iPadOS 26 推出了新的窗口控件。App 可以指定“preferredWindowingControlStyle”和布局参考线来容纳这些控件。 自适应 UI 需要快速响应尺寸变化和方向切换,但某些 App 可能希望临时覆盖“prefersInterfaceOrientationLocked”以锁定屏幕方向。对于需要耗费大量计算资源的操作,请选中“isInteractivelyResizing”以在交互完成后执行操作。 “UIRequiresFullscreen”已弃用。

    • 15:39 - 未来兼容性
    • 借助 iOS 26 SDK,App 无需手动更新或重新提交,也能自动适配新的屏幕尺寸。

    • 16:07 - 后续步骤
    • 要构建灵活的 App,请采用场景生命周期、使用容器视图控制器,并利用布局参考线等 API。

Developer Footer

  • 视频
  • WWDC25
  • 让你的 UIKit App 更加灵活
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载 (英文)
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    获取 Apple Developer App。
    版权所有 © 2025 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则