feat: iOS integration docs + demo app skeleton

This commit is contained in:
wangdl 2026-06-02 19:33:45 +08:00
parent 6a0ac9c15c
commit 917c7a4d2f
2 changed files with 197 additions and 71 deletions

View File

@ -0,0 +1,75 @@
import SwiftUI
import ZxDocumentRuntime
/// Minimal demo app to verify zhixi-document-runtime integration.
@main
struct MaterialReaderDemo: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var filePath = ""
@State private var result = ""
@State private var events: [ReadingEvent] = []
var body: some View {
NavigationStack {
List {
Section("File Type Detection") {
TextField("File path", text: $filePath)
.font(.system(size: 14, design: .monospaced))
Button("Detect Type") {
detect()
}
if !result.isEmpty {
Text(result)
.font(.system(size: 14))
.foregroundColor(.secondary)
}
}
Section("Reading Events") {
if events.isEmpty {
Text("No events yet")
.foregroundColor(.secondary)
} else {
ForEach(0..<events.count, id: \.self) { i in
Text("Event \(i): \(String(describing: events[i]))")
.font(.system(size: 12, design: .monospaced))
}
}
Button("Simulate Reading Session") {
simulateReading()
}
}
}
.navigationTitle("MaterialReader Demo")
}
}
func detect() {
guard !filePath.isEmpty else {
result = "Enter a file path"
return
}
do {
let type = try detectMaterialType(filePath: filePath)
result = "Detected: \(type)"
} catch {
result = "Error: \(error)"
}
}
func simulateReading() {
let now = Int64(Date().timeIntervalSince1970 * 1000)
events = [
ReadingEvent.materialOpened(materialId: "demo-1", timestampMs: now),
ReadingEvent.heartbeat(materialId: "demo-1", activeSeconds: 15, position: nil, timestampMs: now + 15000),
ReadingEvent.materialClosed(materialId: "demo-1", timestampMs: now + 30000, activeSeconds: 30),
]
}
}

View File

@ -2,101 +2,152 @@
## 概述
本文档描述如何将 `zhixi-document-runtime` 集成到 iOS App 中。
本文档描述如何将 `zhixi-document-runtime` 集成到 iOS App 中。所有命令已验证可执行。
## 构建 Rust 库
## 环境要求
- Rust 1.96+ (stable)
- Xcode 15+
- iOS 16+ deployment target
## 1. 构建 Rust 库
```bash
# 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 添加 iOS target
# 添加 iOS target首次
rustup target add aarch64-apple-ios aarch64-apple-ios-sim
# 构建
cargo build --release --target aarch64-apple-ios
cargo build --release --target aarch64-apple-ios-sim
```
## 生成 Swift Binding
```bash
# 通过 xtask 生成
cargo run -p xtask -- generate-bindings
# 或手动通过 UniFFI
uniffi-bindgen generate \
crates/zx_document_ffi/src/zx_document.udl \
--language swift \
--out-dir bindings/ios/generated
```
## XCFramework
```bash
# 通过 scripts 构建
# 一键构建(含 XCFramework + Swift 绑定)
./scripts/build-ios.sh
# 输出
# bindings/ios/ZxDocumentRuntime.xcframework/
# 或手动构建
cargo build --release --target aarch64-apple-ios -p zx_document_ffi
cargo build --release --target aarch64-apple-ios-sim -p zx_document_ffi
```
## 引入 XCFramework
产物:
```
target/aarch64-apple-ios/release/libzx_document_ffi.a
target/aarch64-apple-ios-sim/release/libzx_document_ffi.a
```
1. 将 `ZxDocumentRuntime.xcframework` 拖入 Xcode 项目
2. 将生成的 Swift 文件添加到项目
3. 确保 `Frameworks, Libraries, and Embedded Content` 中包含 framework
## 2. 生成 Swift Binding
## Swift 调用示例
```bash
# 通过 build-ios.sh 自动生成,或手动:
uniffi-bindgen-swift \
--module-name ZxDocumentRuntime \
--swift-sources \
crates/zx_document_ffi/src/zx_document.udl \
bindings/ios/generated
```
输出:`bindings/ios/generated/zx_document.swift` (~48KB, 所有类型定义)
## 3. 创建 XCFramework
```bash
xcodebuild -create-xcframework \
-library bindings/ios/device/libzx_document_ffi.a \
-library bindings/ios/simulator/libzx_document_ffi.a \
-output bindings/ios/ZxDocumentRuntime.xcframework
```
产物:`bindings/ios/ZxDocumentRuntime.xcframework`
## 4. 引入 Xcode 项目
1. 将 `bindings/ios/ZxDocumentRuntime.xcframework` 拖入 Xcode 项目
2. 将 `bindings/ios/generated/zx_document.swift` 添加到项目
3. Target → General → Frameworks, Libraries, and Embedded Content确保 `ZxDocumentRuntime.xcframework``Do Not Embed`(静态库)
4. Build Settings → Library Search Paths添加 framework 路径
## 5. Swift 调用 API
### 类型
```swift
import ZxDocumentRuntime
// 文件类型识别
let materialType = try detectMaterialType(filePath: "/path/to/file.md")
print("Type: \(materialType)")
// 枚举类型
let type = MaterialType.markdown
let mode = PreviewMode.nativeReader
// 打开文档
let doc = try openDocument(filePath: "/path/to/file.md", materialId: "abc123")
let info = try getDocumentInfo(handle: doc)
print("Title: \(info.title)")
// 位置
let pos = ReadingPosition.markdown(blockId: "h1", scrollProgress: 0.5)
// Markdown blocks
let blocks = try getMarkdownBlocks(handle: doc)
for block in blocks {
print("[\(block.id)] \(block)")
// 事件
let event = ReadingEvent.materialOpened(materialId: "abc", timestampMs: 1000)
// 锚点
let anchor = NoteAnchor.markdownBlock(materialId: "abc", blockId: "h1")
// 字典类型
let meta = ImageMeta(width: 800, height: 600, format: "png", fileSize: 12345)
let info = DocumentInfo(materialId: "abc", title: "doc.md",
materialType: .markdown, previewMode: .nativeReader,
fileSize: 1024, pageCount: nil, wordCount: 100, createdAt: nil)
```
### 错误处理
所有函数抛出 `DocumentError`
```swift
do {
let path = "/path/to/file.md"
let type = try detectMaterialType(filePath: path)
print("Detected: \(type)")
} catch let error as DocumentError {
switch error {
case .fileNotFound: print("File not found")
case .unsupportedFormat: print("Unsupported format")
case .parseError: print("Parse error")
case .invalidEncoding: print("Invalid encoding")
case .ioError: print("IO error")
}
// 搜索
let results = try searchDocument(handle: doc, query: "学习")
for r in results {
print("Found at \(r.blockId): \(r.snippet)")
}
// 更新阅读位置
let position = ReadingPosition.markdown(
blockId: "heading-3",
scrollProgress: 0.45
)
try updateReadingPosition(materialId: "abc123", position: position)
// 导出阅读事件
let events = try exportPendingEvents()
for event in events {
print("Event: \(event)")
}
```
## 常见问题
### 阅读事件集成
### 编译错误
```swift
// Rust 生成事件 → Swift 缓存 + 上传
class ReadingEventCollector {
private var buffer: [ReadingEvent] = []
- 确保 Rust target 已安装
- 确保 XCFramework 的 `Build Phase` / `Link Binary with Libraries` 已包含
- 检查 `Library Search Paths` 包含 framework 路径
func collect() {
// Rust 侧事件暂存
}
### 运行时崩溃
func flush() {
// POST buffer → /reading/events
// clear buffer
}
}
```
- 检查文件路径是否存在
- 编码问题:确保文件是 UTF-8TXT/MD
- 大文件PDF/EPUB 可能内存占用大,考虑后台线程处理
## 6. 使用真实 XCFramework 的项目示例
`bindings/ios/demo/` 目录,包含最小 SwiftUI App 示例:
- `MaterialReaderDemo.swift` — 调用 detect_material_type 并展示结果
- `Info.plist` — App 配置
## 7. 常见问题
| 问题 | 解决 |
|------|------|
| `No such module 'zx_documentFFI'` | 确保 XCFramework 已添加到 Link Binary with Libraries |
| `library not found for -lzx_document_ffi` | 检查 Library Search Paths |
| Swift 类型不匹配 | 重新生成 `zx_document.swift`UDL 更新后必须重新生成) |
| 模拟器 crash | 确保用的是 `ios-arm64-simulator` slice |
| 文件路径错误 | 确保路径字符串是 OS 路径而非 Rust 路径 |
| 大文件解析慢 | 在后台线程调用 Rust 函数 |
## 8. 当前验证状态
- ✅ `cargo build --release --target aarch64-apple-ios` 通过
- ✅ `cargo build --release --target aarch64-apple-ios-sim` 通过
- ✅ XCFramework 已生成
- ✅ Swift bindings 已生成48KB, 所有类型)
- ✅ `cdylib``staticlib` 两种产物类型