大多数浏览器和
Developer App 均支持流媒体播放。
-
提升文稿启动体验
通过新的文稿启动体验,让你的文稿类 App 脱颖而出,以独一无二的风格吸引用户的目光。了解如何利用新 API 来自定用户启动你的 App 时看到的第一个屏幕。利用系统提供的新设计,并通过自定操作、赏心悦目的装饰性视图以及令人惊艳的动画效果对它加以修改。
章节
- 0:00 - Introduction
- 1:01 - Design overview
- 2:18 - Getting started with SwiftUI
- 3:13 - Getting started with UIKit
- 4:11 - Customization
- 6:42 - Adding template support to your app
资源
- Building a document-based app with SwiftUI
- Customizing a document-based app’s launch experience
- Forum: UI Frameworks
相关视频
WWDC23
WWDC20
-
下载
大家好!我叫 Julia 是 SwiftUI 团队的一名工程师 在这个视频中 我将介绍 基于文稿的应用程序 并探索全新方式来使它们脱颖而出 采用新的可自定启动体验 来展示基于文稿的 App 以及它的独特风格 采用全新设计 轻松指导用户 创建自己的第一份文稿 为使用模板创建文稿 提供出色的自定选项和有力支持 今天 我将重点介绍一些关键功能 助力打造新的文稿启动体验 你将了解如何将新设计整合到 你的现有应用程序中
最后 我将介绍一些新的 API 助你充分展示 App 的独特性
我们先来看看设计概览 采用了这种新启动体验的 出色 App 示例是 适用于 iPadOS 的 Playgrounds 下面我将介绍相关功能 并展示 Playgrounds 如何 在设计中利用这些功能 来认识一下 Byte 它是 Swift Playgrounds 中的一个角色 它会在开发者学习 Swift 时 提供相应指导和鼓励!
Byte 除了有标题视图前后的 其他装饰性视图 还有前景和背景补充元素 它们让你能够将 App 的独特元素 融入启动体验中 App 的名称显示在正中间
轻点这个主按钮后 Playgrounds 即会打开 一系列编码教程 轻点第二个按钮后 Playgrounds 会创建一个新的 App 并打开相应编辑器 浏览器配置为 显示那些采用这个 App 支持的 文件格式的文稿 可以将背景替换为一种纯色、 一种渐变效果、一张图像 或者你选择的一个自定视图 我喜欢这种全新体验 并想在我的一个 App 中采用它 我正好有一个很好的选择! 我为我的侄女 Yael 打造了一个讲故事的 App 她喜欢创作故事 我想为她提供更出色的写作体验 我使用了一个 DocumentGroup 场景 来指定提供闭包的文稿 以及用于显示打开的文稿的视图 使用 iOS 17 SDK 进行编译后 这个写作 App 会在启动时 显示系统文稿浏览器 现在 如果使用 iOS 18 SDK 进行重新编译 这个 App 会在启动后 显示全新设计 标题视图中间的 “Create Document”按钮 会自动显示 因为这个 App 支持文稿创建 相同的功能也适用于 UIKit 基于文稿的应用程序 那些将 UIDocumentViewController 设置为窗口的根视图控制器的 App 均支持这个功能 如果你的 App 使用 UIDocumentViewController 显示文稿 它很可能是通过 UIDocumentBrowserViewController 呈现的 后者是窗口的 视图控制器层次结构的根 你需要指定浏览器视图控制器的委托 以便在选择后呈现文稿 新的启动体验已包含它自己的浏览器 所以要在 App 中启用新的设计 只需将根替换为 UIDocumentViewController 如需详细了解 UIDocumentViewController 概览 请记得观看 WWDC23 中的 “编译更棒的基于文档的 App” 视频 我们先暂停并总结一下 到目前为止 我们已经介绍了 如何将新设计融入到 App 中 这是一个良好的开端! 下面我想介绍一种新的 API 它能让你 进一步增强 App 的启动体验 我侄女最喜欢粉色 我认为用一张粉色的奇异丛林图像 作为背景 可以为这个写作 App 带来出色的启动体验
为了指定背景 在 SwiftUI App 定义中 我声明了 DocumentGroupLaunchScene 这是 iOS 18 的新增功能 接下来 可以通过为我喜欢的标题 提供一个 NewDocumentButton 来更改 “Create Document”按钮的标题
现在我想在启动体验中 加入一些我的 App 特有的元素 我侄女喜欢的另一样东西是机器人 她对此感到非常兴奋 也许我能启发她自己创作 关于机器人的故事? 我会在设计中加入一个机器人 我想沿着标题视图的前缘 放置机器人图像 沿着后缘放置奇异花朵 为提供补充元素 我将使用补充视图构建器的参数 可以将补充视图视为一个画布 你可以在上面放置所有图像和视图 并相对于 场景边界和标题视图的框架 确定它们的位置 补充视图构建器 提供一个 geometry 参数 其中包含场景和标题视图的框架 使用修饰符 position() 或 offset() 布置补充元素
我想添加两个补充视图 一个沿着标题视图的前缘 另一个是奇异花朵 紧挨着标题视图的后缘 如果你的 App 使用 UIDocumentViewController 来显示 文稿 请使用新的 launchOptions 属性来进行自定设置 viewDidLoad 是 配置文稿视图控制器的 启动选项的理想之选 现在你知道了如何 在设计中添加装饰性视图 机器人和奇异花朵图像已添加完成 使视图更加生动 让它真正变得与众不同 Yael 看到它一定会很高兴!
许多 App 都能够使用 App 定义的模板来创建文稿 对于需要遵循一致的格式、布局 或样式的文稿来说 模板是绝佳的起点 iPadOS 上的 Playgrounds App 提供了一系列丰富的模板 指导开发者如何为视图添加动画效果 识别手势以及播放声音 模板可以存储在磁盘上 也可以从网上下载 比如在 Playgrounds 中 我知道我侄女会喜欢从一些模板中 选择一些故事素材开始她的创作 我们现在就来 添加一个使用模板 创建新文稿的选项! 为此 我需要再添加一个操作按钮 将这个按钮命名为 “Choose a Template” 轻点这个按钮后 SwiftUI 会调用一个异步闭包 要求你返回使用模板创建的文稿 如果你想稍后返回文稿 在显示模板选择器视图后 在闭包中 创建 continuation 并保存 以自定视图形式 显示工作表或全屏覆盖视图 来选择模板 你可以将 continuation 传递给 TemplatePicker 然后在那里恢复它 用户选取模板后 App 会使用这个模板 创建一个文稿 然后通过调用 continuation 将这个文稿返回 SwiftUI SwiftUI 将通过拷贝 给定的模板来创建新文稿 对于 UIKit App 先定义 UIDocument.CreationIntent 来表示你的文稿模板
然后 在 UIDocumentViewController 子类中 将启动选项的主要或次要操作 分配为具有这个意图的 创建文稿操作 要处理创建新文稿的请求 分配启动选项的 浏览器视图控制器的委托 最后 读取浏览器的活动文稿创建意图 以确定要创建的文稿
就是这样 在添加几行代码之后 App 就能支持使用 预设结构和情节类型来创作故事! 你应该选用最适合 你的 App 的自定设计 来构建模板选择器 新的启动体验非常棒 很高兴看到你将它 融入你的 App 中 首先使用 iOS 18 SDK 重新编译你的 SwiftUI App 来进行采用 然后添加 DocumentGroupLaunchScene 来自定启动体验 展示你的 App 的独特风格 对于 UIKit App 将 UIDocumentViewController 设置为根视图控制器 并将你的自定设置 应用于 launchOptions 每个应用程序都有自身的独特之处 通过采用新的 API 可充分展示 App 的独特性 让人一眼就能认出来 感谢观看这个视频! 快通过文稿启动体验 来讲述你的 App 的故事吧!
-
-
2:38 - Document-based application
@main struct WritingApp: App { var body: some Scene { DocumentGroup(newDocument: { StoryDocument() }) { file in StoryView(document: $file.document) } } }
-
3:26 - Presenting a document from the browser in iOS 17
class DocumentViewController: UIDocumentViewController { ... } let documentViewController = DocumentViewController() let browserViewController = UIDocumentBrowserViewController( forOpening: [.plainText] ) window.rootViewController = browserViewController
-
3:38 - Presenting a document from the browser in iOS 17
class DocumentViewController: UIDocumentViewController { ... } let documentViewController = DocumentViewController() let browserViewController = UIDocumentBrowserViewController( forOpening: [.plainText] ) window.rootViewController = browserViewController browserViewController.delegate = self
-
3:43 - Presenting a document from the browser in iOS 17
class DocumentViewController: UIDocumentViewController { ... } let documentViewController = DocumentViewController() let browserViewController = UIDocumentBrowserViewController( forOpening: [.plainText] ) window.rootViewController = browserViewController browserViewController.delegate = self // MARK: UIDocumentBrowserViewControllerDelegate func documentBrowser( _ browser: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL] ) { guard let url = documentURLs.first else { return } documentViewController.document = StoryDocument(fileURL: url) browser.present(documentViewController, animated: true) }
-
3:56 - Presenting a document from the browser in iOS 18
class DocumentViewController: UIDocumentViewController { ... } let documentViewController = DocumentViewController() window.rootViewController = documentViewController
-
4:38 - Customize the document launch experience: background
DocumentGroup( newDocument: { StoryDocument() } ) { file in StoryView(document: $file.document) } DocumentGroupLaunchScene { ... } background: { Image(.pinkJungle) .resizable() .aspectRatio(contentMode: .fill) }
-
4:49 - Customize the document launch experience: new document button title
DocumentGroup( newDocument: { StoryDocument() } ) { file in StoryView(document: $file.document) } DocumentGroupLaunchScene { NewDocumentButton("Start Writing") } background: { Image(.pinkJungle) .resizable() .aspectRatio(contentMode: .fill) }
-
5:29 - Customize the document launch experience: accessory views
DocumentGroupLaunchScene { NewDocumentButton("Start Writing") } background: { Image(.pinkJungle) .resizable() .aspectRatio(contentMode: .fill) } overlayAccessoryView: { }
-
5:44 - Position accessory views
DocumentGroupLaunchScene { NewDocumentButton("Start Writing") } background: { Image(.pinkJungle) .resizable() .aspectRatio(contentMode: .fill) } overlayAccessoryView: { geometry in }
-
5:53 - Position accessory views
DocumentGroupLaunchScene { NewDocumentButton("Start Writing") } background: { ... } 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 ) } }
-
6:11 - Customize the document launch experience in a UIKit app
class DocumentViewController: UIDocumentViewController { override func viewDidLoad() { super.viewDidLoad() // Update the background launchOptions.background.image = UIImage(resource: .pinkJungle) // Add foreground accessories launchOptions.foregroundAccessoryView = ForegroundAccessoryView() } }
-
7:31 - Create a document from a template: add a button
DocumentGroupLaunchScene { NewDocumentButton("Start Writing") NewDocumentButton("Choose a Template", for: StoryDocument.self) { } }
-
7:45 - Create a document from a template: return document later
@State private var creationContinuation: CheckedContinuation<StoryDocument?, any Error>? DocumentGroupLaunchScene { NewDocumentButton("Start Writing") NewDocumentButton("Choose a Template", for: StoryDocument.self) { try await withCheckedThrowingContinuation { continuation in self.creationContinuation = continuation } } }
-
7:56 - Create a document from a template: present a template picker
@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 } }
-
8:07 - Create a document from a template: template picker view
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 } } } extension StoryDocument { static func threeActStructure() -> Self { Self.init(...) } }
-
8:20 - Create a document from a template in UIKit
extension UIDocument.CreationIntent { static let template = UIDocument.CreationIntent("template") }
-
8:29 - Create a document from a template in UIKit
launchOptions.secondaryAction = LaunchOptions.createDocumentAction(with: .template) launchOptions.browserViewController.delegate = self // MARK: UIDocumentBrowserViewControllerDelegate func documentBrowser( _ browser: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, ImportMode) -> Void) { switch browser.activeDocumentCreationIntent { case .template: presentTemplatePicker(with: importHandler) default: let newDocumentURL = // ... importHandler(newDocumentURL, .copy) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。