feat: iOS integration docs + demo app skeleton
This commit is contained in:
parent
6a0ac9c15c
commit
917c7a4d2f
75
bindings/ios/demo/MaterialReaderDemo/DemoApp.swift
Normal file
75
bindings/ios/demo/MaterialReaderDemo/DemoApp.swift
Normal 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),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
```bash
|
||||||
# 安装 Rust
|
# 添加 iOS target(首次)
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
||||||
|
|
||||||
# 添加 iOS target
|
|
||||||
rustup target add aarch64-apple-ios aarch64-apple-ios-sim
|
rustup target add aarch64-apple-ios aarch64-apple-ios-sim
|
||||||
|
|
||||||
# 构建
|
# 一键构建(含 XCFramework + Swift 绑定)
|
||||||
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 构建
|
|
||||||
./scripts/build-ios.sh
|
./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 Binding
|
||||||
2. 将生成的 Swift 文件添加到项目
|
|
||||||
3. 确保 `Frameworks, Libraries, and Embedded Content` 中包含 framework
|
|
||||||
|
|
||||||
## 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
|
```swift
|
||||||
import ZxDocumentRuntime
|
import ZxDocumentRuntime
|
||||||
|
|
||||||
// 文件类型识别
|
// 枚举类型
|
||||||
let materialType = try detectMaterialType(filePath: "/path/to/file.md")
|
let type = MaterialType.markdown
|
||||||
print("Type: \(materialType)")
|
let mode = PreviewMode.nativeReader
|
||||||
|
|
||||||
// 打开文档
|
// 位置
|
||||||
let doc = try openDocument(filePath: "/path/to/file.md", materialId: "abc123")
|
let pos = ReadingPosition.markdown(blockId: "h1", scrollProgress: 0.5)
|
||||||
let info = try getDocumentInfo(handle: doc)
|
|
||||||
print("Title: \(info.title)")
|
|
||||||
|
|
||||||
// Markdown blocks
|
// 事件
|
||||||
let blocks = try getMarkdownBlocks(handle: doc)
|
let event = ReadingEvent.materialOpened(materialId: "abc", timestampMs: 1000)
|
||||||
for block in blocks {
|
|
||||||
print("[\(block.id)] \(block)")
|
// 锚点
|
||||||
|
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 已安装
|
func collect() {
|
||||||
- 确保 XCFramework 的 `Build Phase` / `Link Binary with Libraries` 已包含
|
// Rust 侧事件暂存
|
||||||
- 检查 `Library Search Paths` 包含 framework 路径
|
}
|
||||||
|
|
||||||
### 运行时崩溃
|
func flush() {
|
||||||
|
// POST buffer → /reading/events
|
||||||
|
// clear buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- 检查文件路径是否存在
|
## 6. 使用真实 XCFramework 的项目示例
|
||||||
- 编码问题:确保文件是 UTF-8(TXT/MD)
|
|
||||||
- 大文件:PDF/EPUB 可能内存占用大,考虑后台线程处理
|
见 `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` 两种产物类型
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user