# TencentQianWebView — iOS Objective-C SDK Framework

腾讯电子签 H5 容器 SDK iOS **纯 Objective-C** 实现的 Framework 源码目录，与 `ios-swift/TencentQianWebView` API 对等，适用于 100% OC 代码库的存量客户（避免引入 Swift 运行时）。

## 模块标识

| 项 | 值 |
|---|---|
| 模块名 | `TencentQianWebView` |
| 语言 | Objective-C（ARC，不混编 Swift） |
| Deployment Target | iOS 14.3 |
| 内核 | WKWebView |
| 公共类前缀 | **`TQ`**（Tencent Qian），避免与客户代码命名冲突 |
| 分发方式 | XcodeGen 工程（`ios-objective-c/project.yml`） + 拷贝 framework / CocoaPods |

> **不发布到 SPM**：SPM 默认假设 Swift 模块，OC 工程通过手工 link `TencentQianWebView.framework` 或 CocoaPods（podspec 占位待发版）集成。

## 目录结构

```
TencentQianWebView/
├── Sources/
│   ├── Public/                  # 对外公共 API（含 TQWebViewComponent / TQWebViewComponentController / TQWebViewPrewarmer / TQKeyboardAvoidance 等 .h）
│   ├── Configuration/           # WKWebViewConfiguration 工厂
│   ├── Internal/                # navigation / UI delegate / 国际化 / 日志 / Prewarm（池/manifest/去重）
│   ├── Download/                # 下载策略 + 下载管理器
│   ├── Scheme/                  # qianapp / 外部 scheme 拦截
│   └── Resources/               # 国际化字符串（Base / zh-Hans）
├── TencentQianWebView.h         # Umbrella header
└── Info.plist
```

## 公共 API 速览

### 容器模式 `TQWebView`

```objc
#import <TencentQianWebView/TencentQianWebView.h>

TQWebViewConfig *config = [TQWebViewConfig defaultConfig];
config.topBarTitle = @"电子签署";

TQSignCallbacks *callbacks = [TQSignCallbacks
    callbacksWithOnSignResult:^(TQSignResult *result) {
        NSLog(@"sign result action=%@ result=%@ flowId=%@",
              result.action, result.result, result.flowId);
    }
    onEvent:^(id<TQSdkEvent> event) {
        // 容器层观测事件
    }];

[TQWebView openFromPresenter:self
                         url:signUrl
                      config:config
                   callbacks:callbacks
                    animated:YES
                  completion:nil];
```

### 组件模式 `TQWebViewComponent`

将签署 WebView 嵌入宿主自定义布局，顶栏 / 进度条 / 错误页由宿主自绘。参数面与 Swift 版一一对应；命令对象为独立类 `TQWebViewComponentController`（OC 无嵌套类型）。显式收口方法命名 `invalidate`（ARC 禁止实现 `release` 方法，按平台惯例命名）：

```objc
TQWebViewComponentController *controller = [[TQWebViewComponentController alloc] init];

TQWebViewComponent *component =
    [[TQWebViewComponent alloc] initWithURL:signUrl
                                     config:config
                             hostController:self  // 可选
                               onSignResult:^(TQSignResult *result) { /* 处理结果 */ }
                                 onSdkEvent:^(id<TQSdkEvent> event) { /* 日志/埋点 */ }
                             onTitleChanged:^(NSString *title, BOOL canGoBack) { /* 更新标题 */ }
                          onProgressChanged:^(NSInteger progress, BOOL visible) { /* 显示进度条 */ }
                      onRenderUnrecoverable:^{ /* 显示错误页 + [controller reload] 重试 */ }
                                 controller:controller];

[myView addSubview:component];  // + Auto Layout 约束

// 页面销毁时（viewDidDisappear 确认退出 / dealloc）：[component invalidate];
```

#### 宿主避让工具 `TQKeyboardAvoidance`（组件 / 配置模式）

组件 / 配置模式由宿主持有布局，SDK 不主动避让；一行调用即可接入与容器模式同款的
**底部安全区 + 软键盘避让**（与 Swift 端 `KeyboardAvoidance.apply` 1:1 对齐）：

```objc
// 组件模式：webView 传组件实例；配置模式：传自有 WKWebView
[TQKeyboardAvoidance applyToWebView:component inView:self.view];
```

除底部约束外，`applyToWebView:` 同时自动接入与容器模式同款的**键盘弹出窗口 contentOffset
冻结 + 动画落定后 nearest 补滚**（抑制 WebKit 聚焦 reveal 过期几何的「掉-弹」抖动，
用户手势滚动不受影响；传入非 WKWebView 的普通视图时退化为仅装底部约束）。

调用后不要再为该视图添加其他底部约束；返回句柄生命周期与宿主视图绑定，需提前解除时调 `-remove`。
语义详见 [../../docs/troubleshooting.md Q15](../../docs/troubleshooting.md#q15-h5-底部-fixed-弹框中的输入框被软键盘遮挡三端键盘避让机制)。

> 完整参数面与关键约束详见 [../../docs/usage-guide.md §1.5](../../docs/usage-guide.md#15-组件模式嵌入自定义布局)。

### 配置模式 `TQWebViewConfigurator`

把 SDK 能力叠加到客户已有 `WKWebView` 上：

```objc
TQWebViewConfiguratorHandle *handle = [TQWebViewConfigurator
    applyToWebView:self.webView
    hostController:self
            config:[TQWebViewConfig defaultConfig]
         callbacks:callbacks];

// 退出页面：[handle detachAndDestroy]
// 复用 WebView：[handle detach]
```

### 预热（可选，提升首屏速度）

预热与容器/配置模式**正交**，不预热时行为与现状完全一致，预热是纯增量收益。`configure:` 传 `nil` 用默认配置（首次 `prewarm` 也会以默认 `PROD` 懒初始化）；`MINIMAL` 仅 DNS、收益小，真正提速靠 `LIGHT` / `HEAVY`，按意图触发即可：

```objc
TQPrewarmConfig *cfg = [TQPrewarmConfig defaultConfig];
cfg.environment = TQPrewarmEnvironmentProd;
[TQWebViewPrewarmer configure:cfg];
[TQWebViewPrewarmer prewarm:TQPrewarmLevelLight bizType:@"single-sign"];
[TQWebViewPrewarmer prewarm:TQPrewarmLevelHeavy bizType:@"single-sign"];

// 签署流程结束、不再需要时主动释放预热池实例（页面 WebView 关闭即销毁，无需手动回收）
[TQWebViewPrewarmer releaseAll];
```

> OC `release` 为保留语义，回收方法命名为 `releasePooled` / `releaseAll`。iOS 池实例只加载 `warmup.html` 做网络/进程预热，**永不复用到页面**（`WKWebView` 无 `clearHistory` API）；预热收益靠共享 `WKProcessPool` + `.default()` DataStore 承接。

完整档位语义、触发时机、池生命周期与端差异详见 [../../docs/usage-guide.md §3.5](../../docs/usage-guide.md#35-预热可选提升首屏速度)。

## 单元测试

```bash
bash scripts/test-ios-objectivec.sh
```

测试 target 见同级 `../TencentQianWebViewTests/`，使用 XCTest。

## 与 Swift 版本的差异

| 方面 | Swift 版本 | Objective-C 版本 |
|---|---|---|
| 模块入口 | `enum TencentQianWebView` 命名空间 | `TQWebView` 类（带 `TQ` 前缀） |
| Config 构造 | 命名参数 + 默认值 | `[TQWebViewConfig defaultConfig]` + 属性赋值 |
| 回调 | 闭包 `(SignResult) -> Void` | `void (^)(TQSignResult *)` block |
| 错误处理 | `Result` / 抛出 | `NSError` / 回调 |
| 事件类型判断 | `switch event` 模式匹配 | `[event isKindOfClass:[TQXxxEvent class]]` |

API 行为、Config 字段语义、`SignResult` 结构、UA 注入格式均与 Swift 版完全一致。

## 集成与排错

- 接入指南：[../../docs/usage-guide.md](../../docs/usage-guide.md)
- 接入自检清单：[../../docs/integration-checklist.md](../../docs/integration-checklist.md)
- 排错决策树 + FAQ：[../../docs/troubleshooting.md](../../docs/troubleshooting.md)
- `qianapp://` 协议规范：[../../docs/qianapp-protocol.md](../../docs/qianapp-protocol.md)
