是斯坦福大学 cs193p 公开课程第 15 集的相关笔记。
cs193p 课程介绍:
The lectures for the Spring 2023 version of Stanford University’s course CS193p (Developing Applications for iOS using SwiftUI) were given in person but, unfortunately, were not video recorded. However, we did capture the laptop screen of the presentations and demos as well as the associated audio. You can watch these screen captures using the links below. You’ll also find links to supporting material that was distributed to students during the quarter (homework, demo code, etc.).
cs193p 课程网址: https://cs193p.sites.stanford.edu/2023
1. App 架构与核心协议
1.1 App 协议 (App Protocol
)
- 基础特性:
- 应用中唯一标记
@main
的结构体,作为程序入口。 body
不返回View
而是返回some Scene
(场景容器)。- 示例代码:
struct MemorizeApp: App { @StateObject var game = EmojiMemoryGame() var body: some Scene { WindowGroup { EmojiMemoryGameView(game: game) } } }
- 应用中唯一标记
1.2 Scene 协议 (Scene Protocol
)
- 核心作用:
- 管理顶级视图的容器(如窗口、分屏视图)。
- 可通过
@Environment(\.scenePhase)
监听场景生命周期(活跃、后台等)。
- 常用内置场景类型:
WindowGroup
:非文档应用(如Memorize
),所有窗口共享同一 ViewModel。WindowGroup { EmojiMemoryGameView(game: game) // 所有窗口共享 game 对象 }
DocumentGroup
:文档应用(如EmojiArt
),每个文档独立实例化。
2. 文档架构 (Document Architecture)
2.1 DocumentGroup 的核心机制
- 两种初始化方式:
- 新建文档:
DocumentGroup(newDocument: { ... })
- 打开已有文档:
DocumentGroup(viewing: { ... })
- 新建文档:
- 代码示例:
struct EmojiArtApp: App {
var body: some Scene {
DocumentGroup(newDocument: {
EmojiArtDocument() }) {
config in
EmojiArtDocumentView(document: config.document)
}
}
}
config.document
:系统自动管理的文档实例(需符合ReferenceFileDocument
)。- 强制要求:实现撤销功能(Undo)以保证自动保存。
2.2 文档协议 (FileDocument
vs ReferenceFileDocument
)
-
FileDocument
:- 直接读写文件,实现以下方法:
init(configuration: ReadConfiguration) throws { guard let data = configuration.file.regularFileContents else { throw error } // 从 data 初始化 } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { FileWrapper(regularFileWithContents: /* 转为 Data */) }
- 直接读写文件,实现以下方法:
-
ReferenceFileDocument
(继承ObservableObject
):- 通过快照异步保存(避免阻塞主线程):
func snapshot(contentType: UTType) throws -> Data { return /* 当前 Model 的 Data 表示 */ } func fileWrapper(snapshot: Data, configuration: WriteConfiguration) throws -> FileWrapper { FileWrapper(regularFileWithContents: snapshot) }
- 通过快照异步保存(避免阻塞主线程):
3. 统一文件类型标识 (UTType
)
3.1 定义自定义文件类型
- 反向 DNS 命名:如
edu.stanford.cs193p.emojiart
。 - Xcode 配置步骤:
- 项目设置 → Info → Exported Type Identifiers:
- Identifier:反向 DNS 名称。
- Extensions:文件扩展名(如
.emojiart
)。 - Mime Types:可选 MIME 类型(如
application/emojiart
)。
- 声明文档类型所有权:在 Document Types 中设置应用为 Owner。
- 项目设置 → Info → Exported Type Identifiers:
3.2 代码中声明支持的 UTTypes
- 扩展
UTType
:
extension UTType {
static let emojiart = UTType(exportedAs: "edu.stanford.cs193p.emojiart")
}
- 在文档协议中指定可处理类型:
static var readableContentTypes: [UTType] {
[.emojiart] }
static var writeableContentTypes: [UTType] {
[.emojiart] }
4. 撤销机制 (Undo
)
4.1 集成 UndoManager
- 获取环境中的 UndoManager:
@Environment(\.undoManager) var undoManager
// 通过 Intent 传递给 ViewModel
4.2 实现撤销操作
- 注册撤销动作:
func undoablyPerform(operation: String, with undoManager: UndoManager?, doit: () -> Void) {
let oldModel = model
doit() // 执行修改操作
undoManager?.registerUndo(withTarget: self) {
target in
target.model = oldModel // 撤销逻辑
}
undoManager?.setActionName(operation) // 设置撤销菜单名称(如“Undo Delete”)
}
- 支持重做 (Redo):
- 通过递归注册
undo
,系统自动处理redo
。
- 通过递归注册
5. 通知系统 (Notifications
)
5.1 监听系统通知
- 在 View 中使用
.onReceive
:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) {
_ in
// 处理应用进入后台
}
- 通用订阅方法:
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
forName: .myCustomNotification,
object: nil, //the broadcaster(or nil for "anyone")
queue: .main //the queue on which to dispatch the closure
) {
notification in //closure executed when broadcasts occur
let info: Any? notification.userInfo
// 处理通知
}
// 取消订阅
NotificationCenter.default.removeObserver(observer)
5.2 发布自定义通知
- 代码示例:
NotificationCenter.default.post(
name