关于 uikit-expert
面向实现、审查或改进 iOS 界面的开发者,提供 UIKit 指南。内容按工作流程和领域检查表组织,优先关注正确性,并将性能优化作为可选项。
主要功能
- 01按领域划分的审查清单
- 02现代 API 替换建议
- 03生命周期与 Auto Layout 实践
- 04集合视图与导航模式
- 05内存与并发指导
- 06无障碍与自适应界面指导
适用场景
- 01检查 UIKit 代码的正确性
- 02将弃用的 UIKit API 替换为现代 API
- 03构建集合视图和导航流程
- 04检查无障碍支持与内存处理
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ivan-magda/ uikit-expert-skill --skill uikit-expert安装后在 Claude Code 中输入/uikit-expert确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.tessl-plugin/plugin.jsonreferences/adaptive-appearance.mdanimation-patterns.mdauto-layout.mdcell-configuration.mdconcurrency-main-thread.mdimage-loading.mdkeyboard-scroll.mdlist-performance.mdmemory-management.mdmodern-collection-views.mdmodern-uikit-apis.mdnavigation-patterns.mduikit-swiftui-interop.mdview-controller-lifecycle.md详细介绍
UIKit 专家技能
概述
优先采用原生 API 和 Apple 的官方指导。操作原则:事实优先于架构(不强制采用 MVVM/VIPER/Coordinator,但应分离业务逻辑以便测试);先保证正确性,再考虑性能;只有涉及正确性时才使用“始终/绝不”,可选优化则使用“建议/考虑”。
工作流
以下三种模式都基于下面的核心指南;每个主题都链接到相应的参考文件。选择切入点:
- 审查 — 按照审查清单逐项检查,通过或不通过。对于任何未通过项,都按照对应的核心指南进行修复。优先处理正确性检查项(生命周期、Auto Layout、内存、并发),样式项次之;性能项视为可选。
- 改进 — 针对每个问题,应用“已弃用 → 现代方案”表中的现代替代方案以及对应的核心指南。性能优化(降采样、预取、减少约束变更)应作为可选优化提出,绝不能作为强制要求。
- 实现新功能 — 按以下顺序构建,并在每一步应用相关的核心指南:
- 设计数据流 — 状态所有权、注入依赖、模型层
- 生命周期 — 在
viewDidLoad中进行一次性设置,在viewIsAppearing中处理几何布局 - Auto Layout — 批量激活,避免反复变更
- Collection View — DiffableDataSource + CompositionalLayout + CellRegistration
- 导航 — 覆盖全部 4 个外观插槽,防范并发转场
- 动画、内存(
[weak self]、Task 取消)、并发(@MainActor) - 互操作、图像加载、键盘、动态字体 / VoiceOver / 深色模式
- 使用
#available为 iOS 26+ 功能添加门控,并提供合理的回退方案
核心指南
视图控制器生命周期 — 参见 references/view-controller-lifecycle.md
- 使用
viewDidLoad进行一次性设置:子视图、约束、代理 — 不要处理几何布局 - 使用
viewIsAppearing(向后部署至 iOS 13+)处理依赖几何信息的工作、基于 trait 的布局、滚动到指定项目 viewDidLayoutSubviews会触发多次 — 仅用于轻量级图层 frame 调整viewWillAppear仅用于转场协调器动画和成对的通知注册- 每个生命周期重写方法中都必须调用
super - 子视图控制器容器:按此顺序执行
addChild→addSubview→didMove(toParent:) - 开发期间通过
deinit日志确认对象已释放
Auto Layout — 参见 references/auto-layout.md
- 对以编程方式创建的视图,始终设置
translatesAutoresizingMaskIntoConstraints = false - 使用
NSLayoutConstraint.activate([])— 绝不单独设置.isActive = true - 只创建一次约束,通过切换
isActive或修改.constant来调整 — 绝不移除后重新创建 - 运行时绝不将优先级改为或改出
.required(1000)— 使用 999 - 为约束添加动画:更新 constant → 在超视图的动画代码块中调用
layoutIfNeeded() - iOS 26+:使用
.flushUpdates选项简化约束动画 - 避免在可复用单元格中深度嵌套 UIStackView
- 为关键约束设置
constraint.identifier,让Unable to simultaneously satisfy constraints日志更易读
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false // required on programmatic views
view.addSubview(label)
NSLayoutConstraint.activate([ // one batch = one solve pass
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
Collection View 与数据源 — 参见 references/modern-collection-views.md、references/cell-configuration.md、references/list-performance.md
- 使用
UICollectionViewDiffableDataSource和稳定标识符(UUID/数据库 ID,而非完整模型结构体) - 内容更新时使用
reconfigureItems,仅在单元格类型发生变化时使用reloadItems - 初始填充时使用
applySnapshotUsingReloadData(跳过差异计算) - 对任何非简单布局都使用
UICollectionViewCompositionalLayout - 使用
UICollectionView.CellRegistration— 不使用字符串标识符,也不进行手动类型转换 - 使用
UIContentConfiguration配置单元格内容,使用UIBackgroundConfiguration配置单元格背景 - 使用
configurationUpdateHandler实现由状态驱动的样式(选中、高亮)
// Store the registration once — never create it inside the cell provider (crashes on iOS 15+).
private lazy var cellRegistration = UICollectionView.CellRegistration<PhotoCell, Photo> { cell, _, photo in
cell.configure(with: photo)
}
// Data source is keyed by the stable ID, so the provider receives an id — look up the model, then configure with it.
dataSource = .init(collectionView: collectionView) { [weak self] cv, indexPath, id in
guard let self, let photo = self.photo(for: id) else { return nil }
return cv.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: photo)
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Photo.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(photos.map(\.id))
dataSource.apply(snapshot, animatingDifferences: true)
导航 — 请参阅 references/navigation-patterns.md
- 配置所有 4 个
UINavigationBarAppearance插槽(standard、scrollEdge、compact、compactScrollEdge) - 在
viewDidLoad中为navigationItem(每个 VC 单独配置)设置外观,而不是在viewWillAppear中为navigationBar设置外观 - 使用
setViewControllers(_:animated:)处理深度链接,不要依次调用 push - 防止并发转场 — push/pop 前检查
transitionCoordinator - 在导航栏上仅设置一次
prefersLargeTitles;针对每个 VC 使用largeTitleDisplayMode
动画 — 请参阅 references/animation-patterns.md
UIView.animate— 用于简单的一次性动画;在 completion 中检查finishedUIViewPropertyAnimator— 用于手势驱动、可中断的动画;遵循状态机(inactive → active → stopped)CABasicAnimation— 用于仅限图层的属性(cornerRadius、shadow、3D transforms);先设置模型值- iOS 17+ 弹簧 API:
UIView.animate(springDuration:bounce:)与 SwiftUI 保持一致 - 约束动画:刷新布局 → 更新常量 → 在 superview 上对
layoutIfNeeded()进行动画处理
内存管理 — 请参阅 references/memory-management.md
- 所有逃逸闭包默认使用
[weak self] - Timer:使用带
[weak self]的基于 block 的 API,并在viewWillDisappear中使其失效 - CADisplayLink:使用弱代理模式(没有可用的基于 block 的 API)
- NotificationCenter:在闭包中使用
[weak self],并在deinit中移除观察者 - 嵌套闭包:在存储的内部闭包中重新捕获
[weak self] - 委托:始终使用带
AnyObject约束的weak var delegate: SomeDelegate? - 使用
deinit验证是否释放;如果从未调用,则存在循环引用
并发 — 请参阅 references/concurrency-main-thread.md
UIViewController是@MainActor— 所有子类方法都隐式运行于主 actor- 存储
Task引用,并在viewDidDisappear中取消,而不是在deinit中取消 await后更新 UI 前检查Task.isCancelledTask.detached不会继承 actor 隔离 — 更新 UI 需要显式调用MainActor.run- 切勿从后台调用
DispatchQueue.main.sync— 使用await MainActor.run
UIKit–SwiftUI 互操作 — 请参阅 references/uikit-swiftui-interop.md
- UIHostingController:完整执行子 VC 容器管理(
addChild→addSubview→didMove),并将其保留为存储属性 - 对 Auto Layout 容器使用
sizingOptions = .intrinsicContentSize(iOS 16+) - UIViewRepresentable:在
updateUIView中设置可变状态,而不是在makeUIView中;防止更新循环 - 在 collection view 单元格中使用 UIHostingConfiguration(iOS 16+)显示 SwiftUI 内容
图像加载 — 请参阅 references/image-loading.md
- 解码后的位图大小 = 宽度 × 高度 × 4 字节(12MP 照片约占 48MB RAM)
- 使用 ImageIO 按显示尺寸缩小采样 — 切勿加载完整位图后再调整大小
- iOS 15+:使用
byPreparingThumbnail(of:)或prepareForDisplay()进行异步解码 - 单元格复用:在
prepareForReuse中取消 Task、清除图像,并在完成时验证身份
键盘与滚动 — 请参阅 references/keyboard-scroll.md
- 使用
UIKeyboardLayoutGuide(iOS 15+)— 将内容底部固定到view.keyboardLayoutGuide.topAnchor - iPad:为浮动键盘设置
followsUndockedKeyboard = true - 使用布局指南替换所有手动键盘通知处理
自适应布局与辅助功能 — 请参阅 references/adaptive-appearance.md
- 使用
registerForTraitChanges(iOS 17+)代替已弃用的traitCollectionDidChange - Dynamic Type:使用
UIFont.preferredFont(forTextStyle:)+adjustsFontForContentSizeCategory = true - 深色模式:使用语义颜色(
.label、.systemBackground);在 trait 变化时重新解析 CGColor - VoiceOver:为自定义视图设置
accessibilityLabel、accessibilityTraits、accessibilityHint - 对复杂的列表项操作使用
UIAccessibilityCustomAction
快速参考 — 已弃用 API → 现代 API
| 已弃用 / 旧版 | 现代替代方案 | 引入版本 |
|---|---|---|
traitCollectionDidChange | registerForTraitChanges(_:handler:) | iOS 17 |
| 键盘通知 | UIKeyboardLayoutGuide | iOS 15 |
cell.textLabel / detailTextLabel | UIListContentConfiguration | iOS 14 |
register + 字符串出队 | UICollectionView.CellRegistration | iOS 14 |
对快照调用 reloadItems | reconfigureItems | iOS 15 |
barTintColor / isTranslucent | UINavigationBarAppearance(4 个位置) | iOS 13 |
UICollectionViewFlowLayout(复杂布局) | UICollectionViewCompositionalLayout | iOS 13 |
动画中手动调用 layoutIfNeeded() | .flushUpdates 选项 | iOS 26 |
| 旧版应用生命周期 | UIScene + SceneDelegate | iOS 26 起必须使用 |
ObservableObject + 手动失效 | @Observable + UIObservationTrackingEnabled | iOS 18 |
审查清单
每个领域设一道关卡——需要验证的正确性事项(详情见上方对应的核心指南):
- 生命周期 — 不在
viewDidLoad中获取几何信息(使用viewIsAppearing);每个重写方法都调用super;子 VC =addChild → addSubview → didMove;deinit确认对象已释放 - 自动布局 —
translatesAutoresizingMaskIntoConstraints = false;NSLayoutConstraint.activate([]);避免反复变更(切换isActive/.constant);不要在layoutSubviews中调用setNeedsLayout()(会导致无限循环) - 集合视图 — 使用 diffable 和稳定标识符;使用
reconfigureItems而非reloadItems;快照中不能有重复 ID(会触发BUG_IN_CLIENT崩溃);使用CellRegistration而非字符串出队 - 导航 — 配置全部 4 个外观位置;在
navigationItem上配置,而非navigationBar;添加并发转场保护 - 动画 — API 与使用场景相符;约束动画按顺序刷新 → 更新 → 动画;
CAAnimation先设置模型值 - 内存 — 在逃逸闭包中使用
[weak self];在viewDidDisappear中取消Task;代理使用weak;嵌套闭包中不要重新强引用self - 并发 —
await后检查Task.isCancelled;不要从后台调用DispatchQueue.main.sync;VC 子类上不要重复标注@MainActor - 图像 — 降采样至显示尺寸;单元格加载时在
prepareForReuse中取消,并验证身份;NSCache按解码后字节数设置容量 - 互操作 — 保留
UIHostingController并完整执行视图控制器包含流程;updateUIView防止更新循环 - 键盘 — 使用
UIKeyboardLayoutGuide,而非通知 - 自适应 / 无障碍 — 使用
registerForTraitChanges;支持 Dynamic Type;在特征变化时重新解析 CGColor;为自定义视图设置accessibilityLabel/Traits - iOS 26+ — 使用带回退方案的
#available保护;采用UIScene生命周期;考虑使用UIObservationTrackingEnabled
参考资料
references/view-controller-lifecycle.md— 生命周期顺序、viewIsAppearing、子视图控制器的包含关系references/auto-layout.md— 批量激活、约束变更、优先级、动画、调试references/modern-collection-views.md— 可差异数据源、组合式布局、CellRegistrationreferences/cell-configuration.md— UIContentConfiguration、UIBackgroundConfiguration、configurationUpdateHandlerreferences/list-performance.md— 预取、单元格复用、reconfigureItems、滚动性能references/navigation-patterns.md— 栏外观、并发转场、大标题、深层链接references/animation-patterns.md— UIView.animate、UIViewPropertyAnimator、CAAnimation、弹簧动画references/memory-management.md— 保留循环、[weak self]、Timer/CADisplayLink/嵌套闭包陷阱references/concurrency-main-thread.md— @MainActor、Task 生命周期、Swift 6、GCD 迁移references/uikit-swiftui-interop.md— UIHostingController、UIViewRepresentable、尺寸调整、状态桥接references/image-loading.md— 降采样、解码后位图计算、单元格复用竞态条件references/keyboard-scroll.md— UIKeyboardLayoutGuide、滚动视图内边距、iPad 浮动键盘references/adaptive-appearance.md— 特征变化、动态字体、深色模式、VoiceOver、无障碍功能references/modern-uikit-apis.md— Observation 框架、updateProperties()、.flushUpdates、UIScene、Liquid Glass
评论
支持 Markdown