Highlight
iOS 18 为文档类 App 提供了全新的启动界面,通过
DocumentGroupLaunchScene让开发者自定义背景、装饰视图和模板创建按钮,且仅需用 iOS 18 SDK 重新编译即可零代码获得新设计。
核心内容
文档类 App 一直有个尴尬的问题:启动时用户看到的只是系统自带的文档浏览器——一个冷冰冰的文件列表,毫无品牌辨识度。你的 App 可能是一个写作工具、一个画板、一个代码编辑器,但打开的一瞬间,它跟所有文档 App 看起来一模一样。
iOS 18 给出了答案。只要用 iOS 18 SDK 重新编译,SwiftUI 的 DocumentGroup 和 UIKit 的 UIDocumentViewController 就会自动展示全新的启动界面——居中的 App 名称、醒目的创建按钮、内置文档浏览器,全部由系统提供,无需写一行新代码。
但这只是起点。Apple 同时开放了 DocumentGroupLaunchScene API,允许你替换背景图片、添加装饰视图、自定义按钮文案,甚至支持从模板创建文档。Swift Playgrounds 已经用这套 API 把角色 Byte 放进了启动界面,让你的 App 在第一次打开时就能传达自己的个性。
详细内容
SwiftUI:零代码升级 + 逐步定制
一个最基础的 SwiftUI 文档 App 结构如下(02:38):
@main
struct WritingApp: App {
var body: some Scene {
DocumentGroup(newDocument: { StoryDocument() }) { file in
StoryView(document: $file.document)
}
}
}
关键点:
DocumentGroup同时声明了文档类型和编辑视图- 用 iOS 18 SDK 编译后,App 启动时自动展示新的 launch 界面,无需改动代码
要定制启动界面,在 App body 中加入 DocumentGroupLaunchScene(04:38):
DocumentGroup(
newDocument: { StoryDocument() }
) { file in
StoryView(document: $file.document)
}
DocumentGroupLaunchScene {
NewDocumentButton("Start Writing")
} background: {
Image(.pinkJungle)
.resizable()
.aspectRatio(contentMode: .fill)
}
关键点:
DocumentGroupLaunchScene与DocumentGroup并列声明NewDocumentButton("Start Writing")替换默认的 “Create Document” 按钮文案background闭包接受任意 SwiftUI View,这里用图片填充
装饰视图:用 geometry 定位
添加装饰元素时,需要用 overlayAccessoryView 闭包拿到布局信息(05:53):
DocumentGroupLaunchScene {
NewDocumentButton("Start Writing")
} background: {
Image(.pinkJungle)
.resizable()
.aspectRatio(contentMode: .fill)
} overlayAccessoryView: { geometry in
ZStack {
Image(.robot)
.position(
x: geometry.titleViewFrame.minX,
y: geometry.titleViewFrame.minY
)
Image(.plant)
.position(
x: geometry.titleViewFrame.maxX,
y: geometry.titleViewFrame.maxY
)
}
}
关键点:
geometry.titleViewFrame提供标题视图的精确 frame,包含minX/minY/maxX/maxY- 装饰视图可以放在标题视图的前方或后方,形成层次感
- 用
position()或offset()定位,系统不会自动布局
从模板创建文档
通过第二个 NewDocumentButton 支持模板创建(07:56):
@State private var creationContinuation: CheckedContinuation<StoryDocument?, any Error>?
@State private var isTemplatePickerPresented = false
DocumentGroupLaunchScene {
NewDocumentButton("Start Writing")
NewDocumentButton("Choose a Template", for: StoryDocument.self) {
try await withCheckedThrowingContinuation { continuation in
self.creationContinuation = continuation
self.isTemplatePickerPresented = true
}
}
.sheet(isPresented: $isTemplatePickerPresented) {
TemplatePicker(continuation: $creationContinuation)
}
}
模板选择器中,用户选好后通过 continuation 返回文档(08:07):
struct TemplatePicker: View {
@Binding var creationContinuation: CheckedContinuation<StoryDocument?, any Error>?
var body: some View {
Button("Three Act Structure") {
creationContinuation?.resume(returning: StoryDocument.threeActStructure())
creationContinuation = nil
}
}
}
关键点:
NewDocumentButton的 async 闭包要求返回一个文档实例,SwiftUI 会自动保存- 用
CheckedContinuation挂起闭包,等用户选完模板再 resume - continuation 用完后必须置 nil,避免重复调用
UIKit:launchOptions 定制
UIKit App 通过 UIDocumentViewController 的 launchOptions 属性定制(06:11):
class DocumentViewController: UIDocumentViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Update the background
launchOptions.background.image = UIImage(resource: .pinkJungle)
// Add foreground accessories
launchOptions.foregroundAccessoryView = ForegroundAccessoryView()
}
}
UIKit 模板创建则用 UIDocument.CreationIntent(08:29):
extension UIDocument.CreationIntent {
static let template = UIDocument.CreationIntent("template")
}
launchOptions.secondaryAction = LaunchOptions.createDocumentAction(with: .template)
launchOptions.browserViewController.delegate = self
// In delegate method:
func documentBrowser(
_ browser: UIDocumentBrowserViewController,
didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, ImportMode) -> Void
) {
switch browser.activeDocumentCreationIntent {
case .template:
presentTemplatePicker(with: importHandler)
default:
let newDocumentURL = // ...
importHandler(newDocumentURL, .copy)
}
}
关键点:
- UIKit App 获取新界面的前提是
UIDocumentViewController必须是 window 的 rootViewController - 旧架构中
UIDocumentBrowserViewController作为 root 的模式需要调整 activeDocumentCreationIntent区分不同的创建意图,路由到不同的模板选择逻辑
核心启发
-
做什么:为文档类 App 添加品牌化启动界面。为什么值得做:用户第一次打开 App 就能感受到产品调性,而非面对通用系统界面。怎么开始:用 iOS 18 SDK 编译现有 SwiftUI
DocumentGroup项目,零代码获得新界面,再逐步加背景和装饰。 -
做什么:提供 3-5 个精选文档模板。为什么值得做:降低新用户创建文档的门槛,模板自带结构和样式,用户不必从空白页开始。怎么开始:在
DocumentGroupLaunchScene中加NewDocumentButton("Choose a Template", for:),配合CheckedContinuation实现模板选择流程。 -
做什么:用装饰视图讲述 App 的故事。为什么值得做:装饰元素让启动界面从功能界面变成品牌触点,Swift Playgrounds 的 Byte 角色就是成功案例。怎么开始:在
overlayAccessoryView中用geometry.titleViewFrame定位品牌角色或图标,1-2 个元素即可,避免过度装饰分散注意力。 -
做什么:UIKit 文档 App 迁移 rootViewController。为什么值得做:只有把
UIDocumentViewController设为 root 才能获得新启动界面,旧架构中UIDocumentBrowserViewController作为 root 的模式已不再需要。怎么开始:移除UIDocumentBrowserViewController层级,直接设UIDocumentViewController为 root,然后在viewDidLoad中配置launchOptions。
关联 Session
- Build better document-based apps with UIKit — UIKit 文档 App 的基础架构更新,理解 UIDocumentViewController 的完整用法
- Demystify SwiftUI containers — 深入理解 SwiftUI 容器视图机制,DocumentGroup 的底层原理
- What’s new in SwiftUI — SwiftUI 年度更新总览,包含 DocumentGroupLaunchScene 的上下文
- Extend your app’s controls across the system — 系统级控件扩展,与启动界面按钮定制形成互补
评论
GitHub Issues · utterances