2026-04-21 22:25:33 +08:00
|
|
|
|
# XuqmGroup iOS SDK 文档
|
|
|
|
|
|
|
|
|
|
|
|
> Swift 5.9 · iOS 16+ · macOS 13+ · async/await
|
|
|
|
|
|
|
|
|
|
|
|
## 模块结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
XuqmGroup-iOSSDK/
|
2026-06-05 15:48:08 +08:00
|
|
|
|
├── Package.swift # SPM 包定义
|
|
|
|
|
|
├── XuqmSDK.podspec # CocoaPods 发版描述
|
|
|
|
|
|
├── XuqmWebViewSDK.podspec # WebView 独立模块发版描述
|
|
|
|
|
|
├── PUBLISH.md # 发版操作手册
|
|
|
|
|
|
├── Sources/XuqmCoreSDK/ # 核心:初始化、HTTP、Token
|
|
|
|
|
|
├── Sources/XuqmImSDK/ # IM:WebSocket 实时通信
|
|
|
|
|
|
├── Sources/XuqmPushSDK/ # 推送:APNs Token 注册
|
|
|
|
|
|
├── Sources/XuqmUpdateSDK/ # 版本管理:检查更新
|
|
|
|
|
|
├── Sources/XuqmLicenseSDK/ # 授权管理
|
|
|
|
|
|
├── Sources/XuqmFileSDK/ # 文件上传、下载、本地打开
|
|
|
|
|
|
└── Sources/XuqmWebViewSDK/ # WebView 嵌入式组件 / 独立页面
|
|
|
|
|
|
├── XWebViewBridge.swift # 页面配置 / 控制器桥接
|
|
|
|
|
|
├── XWebViewPage.swift # 独立页面
|
|
|
|
|
|
├── XWebViewTypes.swift # 配置 / 控制器协议
|
|
|
|
|
|
└── XWebViewView.swift # 嵌入式组件
|
2026-04-21 22:25:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 集成
|
|
|
|
|
|
|
|
|
|
|
|
### Swift Package Manager(推荐)
|
|
|
|
|
|
|
|
|
|
|
|
在 Xcode → File → Add Package Dependencies,输入仓库地址:
|
|
|
|
|
|
```
|
|
|
|
|
|
https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git
|
|
|
|
|
|
```
|
|
|
|
|
|
选择版本规则 `Up to Next Major: 0.1.0`。
|
|
|
|
|
|
|
|
|
|
|
|
或在 `Package.swift` 中添加:
|
|
|
|
|
|
```swift
|
|
|
|
|
|
dependencies: [
|
|
|
|
|
|
.package(url: "https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git", from: "0.1.0")
|
|
|
|
|
|
],
|
|
|
|
|
|
targets: [
|
2026-05-07 19:39:48 +08:00
|
|
|
|
.target(name: "YourApp", dependencies: ["XuqmSDK", "XuqmWebViewSDK"])
|
2026-04-21 22:25:33 +08:00
|
|
|
|
]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### CocoaPods
|
|
|
|
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
|
|
# Podfile
|
|
|
|
|
|
source 'https://xuqinmin.com/xuqinmin12/xuqm-specs.git'
|
|
|
|
|
|
source 'https://cdn.cocoapods.org/'
|
|
|
|
|
|
|
|
|
|
|
|
pod 'XuqmSDK', '~> 0.1.0'
|
2026-05-07 19:39:48 +08:00
|
|
|
|
pod 'XuqmWebViewSDK', '~> 0.1.0'
|
2026-04-21 22:25:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 初始化(AppDelegate / @main)
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
import XuqmSDK
|
|
|
|
|
|
|
|
|
|
|
|
@main
|
|
|
|
|
|
struct MyApp: App {
|
|
|
|
|
|
init() {
|
|
|
|
|
|
XuqmSDK.initialize(
|
|
|
|
|
|
appKey: "ak_your_app_key",
|
|
|
|
|
|
appSecret: "as_your_app_secret",
|
|
|
|
|
|
debug: true
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 登录后存储 Token
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
// 调用你的业务登录接口获得 token
|
|
|
|
|
|
await XuqmSDK.setToken(token)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-07 19:39:48 +08:00
|
|
|
|
### 3. WebView 独立模块
|
|
|
|
|
|
|
|
|
|
|
|
`XuqmWebViewSDK` 不需要单独初始化,直接依赖即可使用。
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
import XuqmWebViewSDK
|
|
|
|
|
|
|
|
|
|
|
|
// 嵌入式组件:直接放到任意 SwiftUI 页面中
|
|
|
|
|
|
XWebViewView(
|
|
|
|
|
|
config: XWebViewConfig(
|
|
|
|
|
|
url: "https://example.com",
|
|
|
|
|
|
title: "嵌入式网页"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 独立页面:在导航栈中直接 push
|
|
|
|
|
|
NavigationLink("打开页面模式") {
|
|
|
|
|
|
XWebViewPage(
|
|
|
|
|
|
config: XWebViewConfig(
|
|
|
|
|
|
url: "https://example.com",
|
|
|
|
|
|
title: "独立页面"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Core
|
|
|
|
|
|
|
|
|
|
|
|
### ApiClient
|
|
|
|
|
|
|
|
|
|
|
|
基于 `URLSession`,async/await,自动附加 Bearer Token,统一解析 `ApiResponse<T>`。
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
// 定义响应模型
|
|
|
|
|
|
struct LoginResponse: Decodable {
|
|
|
|
|
|
let token: String
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发起请求
|
|
|
|
|
|
let response: LoginResponse = try await ApiClient.post(
|
|
|
|
|
|
path: "/api/im/auth/login",
|
2026-04-29 15:46:39 +08:00
|
|
|
|
body: ["appKey": appKey, "userId": userId]
|
2026-04-21 22:25:33 +08:00
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### TokenStore
|
|
|
|
|
|
|
|
|
|
|
|
基于 `Keychain` 存储(`kSecClassGenericPassword`),App 重启后自动恢复。
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
await XuqmSDK.setToken("eyJ...") // 存储
|
|
|
|
|
|
let token = XuqmSDK.getToken() // 读取(同步)
|
|
|
|
|
|
await XuqmSDK.setToken(nil) // 清除(登出)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## IM
|
|
|
|
|
|
|
|
|
|
|
|
### ImClient
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
import XuqmSDK
|
|
|
|
|
|
|
|
|
|
|
|
class ChatViewController: UIViewController, ImEventDelegate {
|
|
|
|
|
|
|
|
|
|
|
|
let imClient = ImClient()
|
|
|
|
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
|
|
super.viewDidLoad()
|
|
|
|
|
|
imClient.delegate = self
|
|
|
|
|
|
imClient.connect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息
|
|
|
|
|
|
func sendMessage() throws {
|
|
|
|
|
|
try imClient.send(SendMessageParams(
|
|
|
|
|
|
toId: "user_002",
|
|
|
|
|
|
chatType: .single,
|
|
|
|
|
|
msgType: .text,
|
|
|
|
|
|
content: "Hello!"
|
|
|
|
|
|
))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 撤回消息
|
|
|
|
|
|
func revokeMessage(msgId: String) throws {
|
|
|
|
|
|
try imClient.revoke(msgId: msgId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
deinit { imClient.disconnect() }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - ImEventDelegate
|
|
|
|
|
|
extension ChatViewController {
|
|
|
|
|
|
func onConnected() { print("IM 连接成功") }
|
|
|
|
|
|
func onDisconnected(code: Int, reason: String) { print("断开: \(code)") }
|
|
|
|
|
|
func onMessage(_ msg: ImMessage) { /* 更新 UI */ }
|
|
|
|
|
|
func onRevoke(msgId: String, operatorId: String) { /* 标记消息已撤回 */ }
|
|
|
|
|
|
func onError(_ error: Error) { print("错误: \(error)") }
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ImMessage 结构
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
public struct ImMessage: Decodable {
|
|
|
|
|
|
public let id: String
|
|
|
|
|
|
public let fromId: String
|
|
|
|
|
|
public let toId: String
|
|
|
|
|
|
public let chatType: ChatType // single / group
|
|
|
|
|
|
public let msgType: MsgType
|
|
|
|
|
|
public let content: String
|
|
|
|
|
|
public let extra: String?
|
|
|
|
|
|
public let revoked: Bool
|
|
|
|
|
|
public let createdAt: String
|
2026-04-29 00:37:02 +08:00
|
|
|
|
public let editedAt: Int64?
|
2026-04-21 22:25:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 消息类型(MsgType)
|
|
|
|
|
|
|
|
|
|
|
|
`text` / `image` / `video` / `audio` / `file` / `custom` / `location` / `notify` / `richText` / `callAudio` / `callVideo` / `forward`
|
|
|
|
|
|
|
|
|
|
|
|
### 自动重连
|
|
|
|
|
|
|
|
|
|
|
|
断线后指数退避重连,初始 3s,最大 30s。`disconnect()` 后停止。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Push
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
import XuqmSDK
|
|
|
|
|
|
|
|
|
|
|
|
// 在 didRegisterForRemoteNotificationsWithDeviceToken 中调用
|
|
|
|
|
|
func application(_ application: UIApplication,
|
|
|
|
|
|
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
|
|
|
|
Task {
|
2026-04-29 15:46:39 +08:00
|
|
|
|
let token = deviceToken.map { String(format: "%02x", $0) }.joined()
|
2026-04-21 22:25:33 +08:00
|
|
|
|
try await PushSDK.registerToken(
|
2026-04-29 15:46:39 +08:00
|
|
|
|
token,
|
|
|
|
|
|
userId: "user_001"
|
2026-04-21 22:25:33 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`PushSDK.registerToken` 内部将 `Data` 转换为十六进制字符串,vendor 固定为 `APNS`。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Update
|
|
|
|
|
|
|
|
|
|
|
|
### 检查原生版本更新
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
import XuqmSDK
|
|
|
|
|
|
|
2026-04-29 15:46:39 +08:00
|
|
|
|
let result = try await UpdateSDK.checkAppUpdate(currentVersionCode: 123)
|
2026-04-21 22:25:33 +08:00
|
|
|
|
if result.needsUpdate, let info = result.info {
|
|
|
|
|
|
print("最新版本: \(info.versionName)")
|
|
|
|
|
|
// iOS 只能跳转 App Store
|
|
|
|
|
|
if let url = URL(string: info.appStoreUrl) {
|
|
|
|
|
|
await UIApplication.shared.open(url)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-28 21:05:06 +08:00
|
|
|
|
`UpdateSDK` 这里只负责 iOS App 版本更新。RN 热更新如果需要,走独立的 RN SDK / RN 更新模块,不并入统一发版能力。
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
2026-05-07 19:39:48 +08:00
|
|
|
|
`XuqmWebViewSDK` 提供独立的 WebView 组件和页面,可与 `XuqmSDK` 同时使用,也可单独依赖。
|
|
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-05 15:48:08 +08:00
|
|
|
|
## XuqmFileSDK
|
|
|
|
|
|
|
|
|
|
|
|
文件上传、下载、本地打开的统一入口。`XuqmImSDK` 和 `XuqmWebViewSDK` 均依赖此模块。
|
|
|
|
|
|
|
|
|
|
|
|
### 上传
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
import XuqmFileSDK
|
|
|
|
|
|
|
|
|
|
|
|
let result: FileUploadResult = try await FileSDK.shared.upload(
|
|
|
|
|
|
fileURL: localFileURL,
|
|
|
|
|
|
thumbnailData: thumbnailJpegData // 可选
|
|
|
|
|
|
)
|
|
|
|
|
|
// result.url / .thumbnailUrl / .hash / .size / .mimeType / .ext
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 下载
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
// 下载存储目标
|
|
|
|
|
|
public enum FileDownloadDestination: Sendable {
|
|
|
|
|
|
case sandbox // App Documents 目录(在 Files App 中以 App 名称显示)
|
|
|
|
|
|
case userPick // 弹出系统存储面板,由用户选择保存位置
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// URL 下载,实时进度回调
|
|
|
|
|
|
let localURL: URL = try await FileSDK.shared.download(
|
|
|
|
|
|
url: "https://example.com/report.pdf",
|
|
|
|
|
|
fileName: "report.pdf", // 可选,默认从 URL 推断
|
|
|
|
|
|
destination: .sandbox,
|
|
|
|
|
|
onProgress: { progress in // 0–100
|
|
|
|
|
|
print("下载进度:\(progress)%")
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Blob / base64 数据写盘
|
|
|
|
|
|
let localURL: URL = try FileSDK.shared.saveBlobDownload(
|
|
|
|
|
|
base64Data: b64String,
|
|
|
|
|
|
fileName: "export.xlsx",
|
|
|
|
|
|
destination: .userPick
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## XuqmWebViewSDK
|
|
|
|
|
|
|
|
|
|
|
|
### XWebViewConfig 完整参数
|
|
|
|
|
|
|
|
|
|
|
|
| 参数 | 类型 | 默认值 | 说明 |
|
|
|
|
|
|
|------|------|--------|------|
|
|
|
|
|
|
| `url` | String | `""` | 初始加载地址 |
|
|
|
|
|
|
| `title` | String | `""` | 页面标题(独立页面模式使用) |
|
|
|
|
|
|
| `hideToolbar` | Bool | `false` | 隐藏独立页面顶栏 |
|
|
|
|
|
|
| `hideStatusBar` | Bool | `false` | 隐藏状态栏 |
|
|
|
|
|
|
| `userAgent` | String? | `nil` | 自定义 User-Agent |
|
|
|
|
|
|
| `injectedJavaScript` | String? | `nil` | 页面加载后注入的额外 JS |
|
|
|
|
|
|
| `downloadDestination` | FileDownloadDestination | `.sandbox` | 下载文件存储目标 |
|
|
|
|
|
|
| `onMessage` | `(String) -> Void`? | `nil` | H5 发送消息的回调 |
|
|
|
|
|
|
|
|
|
|
|
|
### 文件选择与拍照
|
|
|
|
|
|
|
|
|
|
|
|
WKWebView 原生支持 `<input type="file">`,SDK 额外处理:
|
|
|
|
|
|
|
|
|
|
|
|
- `<input type="file" capture>` 摄像头:iOS 15+ 通过 `requestMediaCapturePermissionFor` 自动授权
|
|
|
|
|
|
- 麦克风 / 摄像头 WebRTC `getUserMedia()`:自动授权
|
|
|
|
|
|
|
|
|
|
|
|
### 下载拦截与存储
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
XWebViewView(
|
|
|
|
|
|
config: XWebViewConfig(
|
|
|
|
|
|
url: "https://example.com",
|
|
|
|
|
|
downloadDestination: .userPick // 弹出系统 Files 存储面板
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `.sandbox`:下载完成后弹出 `UIActivityViewController`(分享 / 存储到 Files)
|
|
|
|
|
|
- `.userPick`:下载完成后弹出 `UIDocumentPickerViewController`(直接选择存储位置)
|
|
|
|
|
|
|
|
|
|
|
|
### H5 监听下载进度
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 下载进度(0–100)
|
|
|
|
|
|
window.addEventListener('__xwvDownloadProgress', (e) => {
|
|
|
|
|
|
console.log(e.detail.url, e.detail.progress)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 下载完成
|
|
|
|
|
|
window.addEventListener('__xwvDownloadDone', (e) => {
|
|
|
|
|
|
if (e.detail.success) {
|
|
|
|
|
|
console.log('下载成功', e.detail.url)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('下载失败', e.detail.error)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### H5 ↔ Native 消息通信
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// H5 → Native(触发 onMessage 回调)
|
|
|
|
|
|
window.webkit.messageHandlers.ReactNativeWebView.postMessage(
|
|
|
|
|
|
JSON.stringify({ type: 'login', token: '...' })
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
// Native → H5
|
|
|
|
|
|
XWebViewBridge.shared.currentController()?.postMessageToWeb(
|
|
|
|
|
|
"window.dispatchEvent(new CustomEvent('nativeMsg', { detail: { key: 'value' } }))"
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
## 发版
|
|
|
|
|
|
|
|
|
|
|
|
### SPM(推荐)
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git add -A && git commit -m "release: 0.1.0"
|
|
|
|
|
|
git tag 0.1.0
|
|
|
|
|
|
git push origin main 0.1.0
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Xcode 用户在 Package Dependencies 刷新即可获取新版本。
|
|
|
|
|
|
|
|
|
|
|
|
### CocoaPods 私有 Spec
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 首次(只需一次)
|
|
|
|
|
|
pod repo add xuqm-specs https://xuqinmin.com/xuqinmin12/xuqm-specs.git
|
|
|
|
|
|
|
|
|
|
|
|
# 每次发版
|
|
|
|
|
|
pod spec lint XuqmSDK.podspec --allow-warnings
|
|
|
|
|
|
pod repo push xuqm-specs XuqmSDK.podspec --allow-warnings
|
|
|
|
|
|
```
|