Highlight
Xcode 12 通过
LibraryContentProvider让开发者把自定义 SwiftUI View 和 modifier 注册成可拖拽的LibraryItem,Xcode 扫描源码后会在 Library 中显示这些条目,并在插入时保持 Preview 继续渲染。
核心内容
SwiftUI 项目变大以后,可复用 View 往往藏在不同文件和 package 里。团队成员知道系统的 Button、Text 可以从 Xcode Library 拖出来,却未必知道项目里已经有一个写好的 SmoothieRowView。如果每次都靠搜索文件名和手写初始化参数,Preview 的可视化编辑优势会被打断。
Xcode 12 把这件事放回 Swift 代码里解决。开发者声明一个符合 LibraryContentProvider 的类型,把重要的 View 或 modifier 包装成 LibraryItem。这些条目会出现在 Xcode Library,用户可以拖进代码或 Canvas,插入的内容仍是可编译的 Swift 示例,Preview 也会继续刷新。
演示项目 Fruta 里,SmoothieRowView 原本只是在多个界面里复用的普通 SwiftUI View。把它注册到 Library 后,Xcode 会自动发现这个条目,为它生成项目分类和标题。插入时使用 .lemonberry 作为占位数据,参数会被 tokenized,开发者可以直接替换成当前列表里的 smoothie。
View 不是唯一入口。演讲后半段把 Image 上常见的 resizable、aspectRatio、frame 三连调用抽成 resizedToFill(width:height:),再把它放进 modifiers library。Xcode 用 base 参数分清被修饰的对象和真正要插入的 modifier,最后只把 modifier 调用写回代码。
这个机制不要求运行项目。Xcode 会扫描 workspace 里的源码,寻找 LibraryContentProvider 声明;这些代码不会在发布版本执行,也会在构建分发产物时被剥离。因为扫描范围包含依赖项,Swift package 也可以把自己的 View 暴露到 Library 中,Fruta 示例里的 Nutrition Facts package 就通过这种方式提供了 CalorieCountView。
详细内容
LibraryContentProvider 是入口(01:57)
要扩展 Xcode Library,先声明一个符合 LibraryContentProvider 的类型。协议有两个入口:views 提供 View 条目,modifiers(base:) 提供 modifier 条目。两者都返回 LibraryItem 数组。
public protocol LibraryContentProvider {
@LibraryContentBuilder
var views: [LibraryItem] { get }
@LibraryContentBuilder
public func modifiers(base: ModifierBase) -> [LibraryItem]
}
关键点:
views用来扩展 Xcode 的 views library。modifiers(base:)用来扩展 modifiers library。@LibraryContentBuilder允许一个 provider 返回多个LibraryItem。- Provider 放在 Swift 代码旁边,编译器能在 API 变化时帮助发现条目是否还能工作。
一个条目就是一段可插入的完成内容(02:32)
LibraryItem 最少需要一段会被插入的 completion。它还可以带 visibility、title、category。演示里,SmoothieRowView 使用 .lemonberry 作为默认数据,category 选择 .control。
LibraryItem(
SmoothieRowView(smoothie: .lemonberry),
visible: true,
title: "Smoothie Row View",
category: .control
)
关键点:
- 第一行的
SmoothieRowView(smoothie: .lemonberry)是用户选择条目后插入到代码里的内容。 visible控制条目是否展示在 Library 中。title避免 Xcode 只根据 completion 推断标题。category: .control让条目进入更明确的功能分类,演示中图标也随之变成 control 风格。
同一个 View 可以有多个配置(03:22)
演讲里强调 View 和 LibraryItem 不需要一一对应。SmoothieRowView 可以有普通版本,也可以有打开 showNearbyPopularity 的版本。第二个条目用自定义 title 说明差异。
struct LibraryContent: LibraryContentProvider {
@LibraryContentBuilder
var views: [LibraryItem] {
LibraryItem(
SmoothieRowView(smoothie: .lemonberry),
category: .control
)
LibraryItem(
SmoothieRowView(smoothie: .lemonberry, showNearbyPopularity: true),
title: "Smoothie Row View With Popularity",
category: .control
)
}
}
关键点:
LibraryContent这个类型名没有特殊含义,演示只是用它承载 provider。- 第一个
LibraryItem插入普通SmoothieRowView。 - 第二个
LibraryItem插入带showNearbyPopularity: true的配置。 title让用户在 Library 搜索结果里区分两个相似条目。
modifier 先从重复调用中抽出来(08:57)
Fruta 项目里经常把 resizable、aspectRatio、frame 连续用在图片上。演讲先把这三步封装成 Image extension,再把这个能力加入 modifiers library。
extension Image {
func resizedToFill(width: CGFloat, height: CGFloat) -> some View {
return self
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: width, height: height)
}
}
关键点:
resizedToFill(width:height:)声明在Image上,所以后面的 provider 要以Image作为 base。resizable()让图片可以改变尺寸。aspectRatio(contentMode: .fill)保持宽高比例并填满目标区域。frame(width:height:)给最终视图设置宽高。
base 告诉 Xcode 哪部分会被保留(09:17)
注册 modifier 时,modifiers(base:) 的参数类型要匹配 modifier 所在的类型。这里是 Image。Xcode 通过 base 区分被修饰对象和要插入的 modifier,用户选择条目后只会得到 .resizedToFill(width:height:) 这段调用。
@LibraryContentBuilder
func modifiers(base: Image) -> [LibraryItem] {
LibraryItem(
base.resizedToFill(width: 100.0, height: 100.0)
)
}
关键点:
base: Image对应Imageextension 上的resizedToFill。LibraryItem的 completion 必须包含base,这样 Xcode 才知道 modifier 附着在哪里。100.0是示例参数,插入后参数会 tokenized,开发者可以按上下文替换。- 这个条目出现在 modifiers tab,而不是 views tab。
Xcode 扫描源码生成 Library 内容(10:41)
演讲最后说明,Xcode 不需要 build 或 run 项目就能填充 Library。它会扫描源码里的 LibraryContentProvider 并解析声明,所以 UI 重构到一半、项目暂时跑不起来时,Library 内容仍然可以贡献出来。
这套扫描也覆盖 workspace 里的依赖。Fruta 依赖的 Nutrition Facts Swift package 暴露了 CalorieCountView,Xcode 会为 package 创建分类。开发者不打开 package 源码,也能在 Library 中找到并插入这个 View。
核心启发
-
设计系统组件面板 做什么:把项目里的按钮、卡片、列表行、空状态 View 注册到 Xcode Library。 为什么值得做:演讲里的核心痛点是 API 可发现性,Library 是开发者寻找可视内容的入口。 怎么开始:为组件所在模块添加
LibraryContentProvider,在views里用真实 sample data 返回多个LibraryItem。 -
为同一个组件提供常用状态 做什么:给一个 View 准备 normal、loading、empty、error 或带附加信息的多个 Library 条目。 为什么值得做:
SmoothieRowView在演示中用两个LibraryItem表示普通版本和 popularity 版本。 怎么开始:在同一个viewsbuilder 中多次创建LibraryItem,用title写清每个配置的用途。 -
把高频 modifier 链做成可插入条目 做什么:把图片裁剪、圆角、阴影、固定尺寸等重复 modifier 链封装后放进 modifiers library。 为什么值得做:演讲把
resizable、aspectRatio、frame合成resizedToFill(width:height:),减少重复手写。 怎么开始:先在目标类型上写 extension,再实现modifiers(base:),用base.yourModifier(...)返回LibraryItem。 -
给 Swift package 附带可视示例 做什么:在共享 package 里为公开 View 提供 Library 内容,让使用方从 Xcode Library 直接发现它。 为什么值得做:Xcode 会扫描 workspace 依赖,演示中的 Nutrition Facts package 因此出现在 Library 里。 怎么开始:把 provider 放在 package 源码中,给每个公开 View 准备可编译的 sample data 和清晰标题。
关联 Session
- Structure your app for SwiftUI previews — 讲如何用 sample data、数据流和输入设计,让 SwiftUI views 更容易预览和测试。
- Visually edit SwiftUI views — 展示用 Xcode Previews canvas 迭代 SwiftUI 界面,并在不同环境下检查 UI。
- Introduction to SwiftUI — 从 declarative code、SwiftUI view 组合和 Xcode 协作开始,为自定义 View 的 Library 条目提供基础。
- What’s new in SwiftUI — 概览 2020 年 SwiftUI 的 app、scene、controls、styles 和 widgets 更新,适合接着扩展可复用组件。
评论
GitHub Issues · utterances