WWDC Quick Look 💓 By SwiftGGTeam
What’s new in UIKit

What’s new in UIKit

观看原视频

Highlight

iOS 18 的 UIKit 支持用 SwiftUI Animation 驱动 UIView 动画,手势系统跨框架统一了依赖关系,Tab Bar 自动适配 sidebar 形态。


核心内容

每个用 UIKit 写 iPad 应用的开发者都遇到过同一个问题:UITabBarController 的 tab bar 和 sidebar 是两套独立的 UI,切换时需要手动管理状态,代码写起来又长又容易出 bug。iPadOS 18 终于把这两者合为一体——新的 UITabUITabGroup API 让你只描述一次 app 结构,系统自动生成 tab bar 和 sidebar 两种形态,用户还可以通过拖拽来自定义 tab 顺序。

同样让人头疼的还有 trait 变更的处理。以前需要在 registerForTraitChanges 里手动注册每一个关心的 trait,再在回调里调用 setNeedsLayoutsetNeedsDisplay。iOS 18 引入 automatic trait tracking 后,只要在 layoutSubviewsdrawRect 等方法里读取了某个 trait,UIKit 就自动记住这个依赖,下次 trait 值变化时自动触发对应的 invalidation。这意味着大量样板代码可以直接删掉。

跨框架协作的痛点也在被消除。SwiftUI 的 Animation 类型现在可以直接驱动 UIKit view 的动画,包括 CustomAnimation;手势系统也做了统一,UIKit 和 SwiftUI 的手势之间可以设置 failure/requirement 关系,还新增了 UIGestureRecognizerRepresentable 协议把 UIKit 手势嵌入 SwiftUI 层级。


详细内容

用 SwiftUI Animation 驱动 UIView

这是本次更新中最实用的 API 变化。UIView.animate(using:) 接受一个 SwiftUI Animation 类型,在交互式动画场景下尤其好用——手势过程中用 .interactiveSpring,手势结束后用 .spring,速度在两阶段之间无缝衔接(03:35):

switch gesture.state {
case .changed:
    UIView.animate(.interactiveSpring) {
        bead.center = gesture.translation
    }

case .ended:
    UIView.animate(.spring) {
        bead.center = endOfBracelet
    }
}

关键点:

  • .changed 分支用 .interactiveSpring,跟随手指移动,回弹幅度适中
  • .ended 分支用 .spring,动画终点固定,速度从手势结束瞬间自然过渡
  • 两段动画共享同一套 velocity 传递机制,不需要手动计算初速度

跨框架手势依赖

当 SwiftUI 视图嵌套在 UIKit 视图中时,两个框架的手势可能同时识别,产生冲突。iOS 18 允许通过 name 和 delegate 方法设置跨框架的 failure requirement(04:46):

// Inner SwiftUI double tap gesture

Circle()
    .gesture(doubleTap, name: "SwiftUIDoubleTap")


// Outer UIKit single tap gesture

func gestureRecognizer(
    _ gestureRecognizer: UIGestureRecognizer,
    shouldRequireFailureOf other: UIGestureRecognizer
) -> Bool {
    other.name == "SwiftUIDoubleTap"
}

关键点:

  • SwiftUI 手势通过 name: 参数打标签,UIKit 端通过 other.name 匹配
  • UIKit 的单击手势要求 SwiftUI 双击手势失败后才触发,避免同时识别
  • 这是 UIGestureRecognizerDelegate 的标准方法,不需要新的协议

Automatic trait tracking

layoutSubviews 中读取 trait,UIKit 自动追踪依赖(06:40):

class MyView: UIView {
    override func layoutSubviews() {
        super.layoutSubviews()

        if traitCollection.horizontalSizeClass == .compact {
            // apply compact layout
        } else {
            // apply regular layout
        }
    }
}

关键点:

  • layoutSubviews 被调用时,UIKit 记录你访问了 horizontalSizeClass
  • 之后该 trait 变化时,UIKit 自动调用 setNeedsLayout
  • 以前需要手动 registerForTraitChanges + 回调中 setNeedsLayout,这段代码可以直接删除
  • 支持的方法包括 layoutSubviewsdrawRect:traitCollectionDidChange 等,完整列表见文档

List environment trait

UICollectionView list section 和 UITableView 中的所有视图现在自动拥有 list environment trait。UIListContentConfigurationUIBackgroundConfiguration 根据这个 trait 自动调整外观(07:43):

func configurations(for location: FileLocation) ->
    (UIListContentConfiguration, UIBackgroundConfiguration) {

    var contentConfiguration = UIListContentConfiguration.cell()
    let backgroundConfiguration = UIBackgroundConfiguration.listCell()

    contentConfiguration.text = location.title
    contentConfiguration.image = location.thumbnailImage

    return (contentConfiguration, backgroundConfiguration)
}

关键点:

  • UIListContentConfiguration.cell() 替代了以前需要手动判断 sidebar/普通样式的逻辑
  • UIBackgroundConfiguration.listCell() 是新增的构造器,自动从 list environment trait 读取外观
  • 新增的构造器还有 .listHeader.listFooter
  • 不再需要把 list appearance 作为参数传入,configuration 在被应用到 cell 时自动适配

替代 CADisplayLink 的新 API,增加了自动 view tracking 和低延迟模式(11:18):

let updateLink = UIUpdateLink(
    view: view,
    actionTarget: self,
    selector: #selector(update)
)
updateLink.requiresContinuousUpdates = true
updateLink.isEnabled = true

@objc func update(updateLink: UIUpdateLink,
                  updateInfo: UIUpdateInfo) {
    view.center.y = sin(updateInfo.modelTime)
        * 100 + view.bounds.midY
}

关键点:

  • 构造时绑定 view,view 加入可见 window 时自动激活,移除时自动停用
  • updateInfo.modelTime 提供与 display 刷新同步的时间,比手动计时更准确
  • requiresContinuousUpdates = true 时每帧都触发;设为 false 则只在有其他更新驱动时触发(如手势、layer 变化)
  • 支持低延迟模式,适合绘图类应用

其他更新

  • Zoom transition:iOS 18 新增 zoom transition,支持 navigation 和 presentation,且全程可交互——可以在转场过程中拖拽中断(02:50
  • SF Symbols 动画:新增 .wiggle.breathe.rotate 三种预设动画,以及 .periodic(指定重复次数和间隔)和 .continuous 行为。Magic Replace 自动处理 badge 和 slash 的过渡动画(12:47
  • UICanvasFeedbackGenerator:新增的反馈生成器,适合大型画布应用,创建时绑定 view,触发反馈时传入位置坐标(15:02
  • Text formatting panel:UITextView 新增格式化面板,包含字体、字号、列表、高亮等控件,通过 textFormattingConfiguration 自定义布局,也可设为 nil 退出(16:14
  • Text highlight:通过 textHighlightStyletextHighlightColorScheme 两个属性实现文本高亮,有 5 种预设配色方案(16:49
  • Writing Tools:UITextView 默认集成 Writing Tools UI,editable text view 获得内联编辑支持,non-editable 获得叠加面板(17:40
  • Apple Pencil Pro:squeeze 手势、barrel-roll 角度、undo slider,以及 PKToolPicker 支持自定义工具(19:01

核心启发

  • 做什么:用 UIView.animate(.spring) 替代 UIViewPropertyAnimator 编写交互式动画。为什么值得做:SwiftUI spring 的 velocity 传递在交互/非交互阶段之间无缝衔接,代码量减少一半以上。怎么开始:在手势的 .changed.ended 分支分别调用 UIView.animate(.interactiveSpring)UIView.animate(.spring),删除旧的 animator 代码。

  • 做什么:把 UITabBarItem 数组迁移到 UITab / UITabGroup 结构。为什么值得做:新 API 自动适配 tab bar 和 sidebar 两种形态,Mac Catalyst 和 visionOS 上也能获得原生体验,不再需要为不同平台写两套导航逻辑。怎么开始:在 UITabBarControllerviewDidLoad 中用 UITabUITabGroup 构建 tabs 数组,删除 tabBar.items 的手动设置。

  • 做什么:删除 registerForTraitChanges 中的手动 trait 注册代码。为什么值得做:automatic trait tracking 在 layoutSubviews 等方法中自动追踪 trait 依赖,手动注册是多余的。怎么开始:在 Xcode 中全局搜索 registerForTraitChanges,逐一检查对应 trait 是否在 layoutSubviewsdrawRect: 中已被访问,如果是就删掉注册代码。

  • 做什么:用 UIListContentConfiguration.cell()UIBackgroundConfiguration.listCell() 替代手动判断 list 样式的代码。为什么值得做:list environment trait 自动适配 sidebar 和 insetGrouped 外观,cell 在旋转屏幕或进入 split view 时不需要手动 reconfigure。怎么开始:找到 cell 配置代码中判断 isSidebar 的分支,替换为统一的 .cell() / .listCell() 构造器。


关联 Session

评论

GitHub Issues · utterances