WWDC Quick Look 💓 By SwiftGGTeam
Evolve your document launch experience

Evolve your document launch experience

观看原视频

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 中加入 DocumentGroupLaunchScene04:38):

DocumentGroup(
    newDocument: { StoryDocument() }
) { file in
    StoryView(document: $file.document)
}

DocumentGroupLaunchScene {
    NewDocumentButton("Start Writing")
} background: {
    Image(.pinkJungle)
        .resizable()
        .aspectRatio(contentMode: .fill)
}

关键点:

  • DocumentGroupLaunchSceneDocumentGroup 并列声明
  • 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 通过 UIDocumentViewControllerlaunchOptions 属性定制(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.CreationIntent08: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

评论

GitHub Issues · utterances