What's new in UIKit
Swift & UI 进阶 20m

UIKit 新特性:预览、Trait 系统、动画符号与空状态

What's new in UIKit

2023年6月5日

在 Apple 官方观看视频

一句话判断

UIKit 在 iOS 17 迎来几项重量级更新:Xcode 预览支持、viewIsAppearing 回调、自定义 Trait、动画符号、空状态 API——每一项都直击日常开发的痛点。

这场 Session 讲了什么

Dima 从 UIKit 团队出发,梳理了 iOS 17 中 UIKit 的五大新特性:

Xcode 预览支持 UIKit。这是很多 UIKit 开发者期待已久的功能。用 #Preview 宏就能直接预览 UIViewController 或 UIView,不需要 SwiftUI 做中间层。可以设置属性来配置预览数据,也可以跨不同配置批量测试。

viewIsAppearing 回调。填补了 viewWillAppearviewDidAppear 之间的空白。它被调用时,视图已经加入层级、trait collection 已更新、视图已经被布局。Dima 把它称为”金发姑娘回调”——不太早、不太晚、不太频繁,刚刚好。关键是它向后兼容到 iOS 13。

Trait System 升级。支持自定义 Trait、新的 trait 覆写 API、闭包式变化回调、SwiftUI 环境键桥接。有专门一场 Session “Unleash the UIKit trait system” 深入讲解。

动画符号(Animated Symbols)。UIImageView 新增 addSymbolEffect() 方法,支持弹跳、变色等内置效果,也可以用 setSymbolImage() 做符号之间的过渡动画。

UIContentUnavailableConfiguration。新的空状态 API,提供 .empty().loading().search() 三种预设配置,也可以用 SwiftUI 视图自定义。配合 updateContentUnavailableConfiguration(using:state) 方法,实现响应式的空状态展示。

值得深挖的点

viewIsAppearing 的精确定位。它在 viewWillAppear 之后被调用,但在同一个 CATransaction 中。这意味着你在 viewIsAppearing 中做的修改会和 viewWillAppear 中的修改在同一帧生效。对比 viewDidAppear,它在一个独立的事务中执行,任何修改都要等过渡动画结束才可见。

空状态配置的生命周期setNeedsUpdateContentUnavailableConfiguration() 触发更新,系统调用 updateContentUnavailableConfiguration(using:state)。这个模式和 cell 配置的更新模式一致,熟悉 updateContentUnavailableConfiguration 机制的开发者上手很快。

动画符号的持续性bounce 效果只播放一次,variableColor 效果会持续播放直到手动移除。用 removeSymbolEffect() 停止动画。

代码片段

// Xcode 预览 UIKit 视图控制器
#Preview("设置页面") {
    let vc = SettingsViewController()
    vc.userName = "张三"
    vc.isDarkModeEnabled = true
    return vc
}

// 直接预览 UIView
#Preview("自定义按钮") {
    let button = CustomButton()
    button.setTitle("点击", for: .normal)
    return button
}
// viewIsAppearing — 最佳初始化时机
override func viewIsAppearing(_ animated: Bool) {
    super.viewIsAppearing(animated)
    // 此时 trait collection 已更新,视图尺寸已确定
    let isWide = traitCollection.horizontalSizeClass == .regular
    configureLayout(forWideScreen: isWide)
}
// 空状态配置
override func updateContentUnavailableConfiguration(
    using state: UIContentUnavailableConfigurationState
) {
    if searchResults.isEmpty {
        contentUnavailableConfiguration = .search()
    } else {
        contentUnavailableConfiguration = nil
    }
}

// 在适当时机触发更新
searchController.searchResultsUpdater = { [weak self] in
    self?.setNeedsUpdateContentUnavailableConfiguration()
}

最佳实践

  • 把依赖视图几何信息的初始化代码从 viewWillAppear 迁移到 viewIsAppearing
  • 空状态不只是首次启动时才出现,网络断开、搜索无结果等场景都应该有对应的空状态。
  • UIHostingConfiguration 包裹 SwiftUI 视图来实现更灵活的空状态设计。
  • 动画符号要克制使用,过多的弹跳和变色会分散注意力。

还有什么值得关注

  • “Animate symbols in your app” Session 有更多符号动画的细节
  • 字体方面新增了动态行高调整,解决某些语言文字被裁剪的问题
  • 新的 locale-aware 图片请求 API,可以根据语言环境返回不同图片
  • 改进的断字和换行规则,对 CJK 文本的排版有提升
WWDC 2023