闭合 Phase 1.17 联调最后一公里(解决 Xcode 日志 4 个 "no handler registered"
+ 提前落地 4 个无外部依赖的简单 handler)。
新增 7 个 handler 模块(Source/Bridge/Handlers/):
- ClipboardHandler.swift §3.1 [13][14]剪贴板 UIPasteboard.general
- ShakeHandler.swift §3.1 [7][8][9]摇一摇 + §3.2 [5]shakeEnd 反向
canShake / canVoice 状态 + motionEnded 转发钩子
- VoicePlayingHandler.swift §3.1 [6]actor VoiceCenter.shared 开关位
- DeviceInfoHandler.swift §3.1 [21]+ §3.2 [1]getphoneInfo 大写 I 入、
getphoneinfo 小写 i 出反向 callback
DeviceInfoSnapshot 6 字段(IDFA 用 idfv 兜底,
CLAUDE.md 不接 ATT/IDFA)
- BrowserHandler.swift §3.1 [16]UIApplication.open async(Task @MainActor
异步打开,闭包先 cb 不等 open 完成)
- OpenSaomaHandler.swift §3.1 [22]空 stub(契约要求注册)
- StartLocationHandler.swift §3.1 [20]Phase 5 完整实现,Phase 2 stub
扩展 VibratorHandler.swift:
- §3.1 [10]vibrator(原有)+ [11]repeatvibrator(msext RootVC.m:1820 沿用:
实际单次 vibrate 不真循环)+ [12]canclevibrator
BridgeProtocol.swift BridgeData 加 nonisolated:
- 项目默认 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor 让 BridgeData 跨 actor
访问被拦;asString / asDouble / asInt / asBool / asArray / asObject /
subscript 全部标 nonisolated(纯值类型本就该是 isolation-free)
- 影响所有 @Sendable async handler 闭包内的 data?.asXxx 访问
WebContainerViewController 接入:
- 加 private let shakeHandler = ShakeHandler() 持有
- registerBridgeHandlers() 注册 8 个 handler 模块
- 加 canBecomeFirstResponder = true + viewDidAppear becomeFirstResponder
- 加 motionEnded(_:with:) 转发到 shakeHandler.handleMotionEnded
(motion 检测必需 first responder,否则不触发)
BuildProject 通过。重装 app 后 H5 启动应不再报 "no handler registered" 错误。
Plan §5 Phase 2.A 全部勾选(含 startlocation bonus stub + BridgeData
nonisolated 重构)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
71 lines
2.7 KiB
Swift
71 lines
2.7 KiB
Swift
//
|
||
// ShakeHandler.swift
|
||
// ylgamehall
|
||
//
|
||
// H5 → Native handler 三件套 + 反向 callback:
|
||
// startshake / stopshake / SwitchShake → shakeEnd
|
||
//
|
||
// 契约:docs/H5-Native-Contract.md §3.1 [7][8][9] + §3.2 [5]
|
||
// Design:§8.7 ShakeKit
|
||
//
|
||
|
||
import UIKit
|
||
|
||
/// 摇一摇状态控制 + 反向 callback 桥接器(@MainActor 实例化,VC 注入)。
|
||
///
|
||
/// 由 WebContainerViewController 在 viewDidLoad 时创建,并:
|
||
/// - 调用 `register(on:)` 注册 3 个 handler
|
||
/// - 重写 `motionEnded(_:with:)` 转发到 `handleMotionEnded(_:)`
|
||
/// - `viewDidAppear` 时调 `becomeFirstResponder()`(否则 motionEnded 不触发)
|
||
@MainActor
|
||
public final class ShakeHandler {
|
||
|
||
/// 摇一摇总开关(startshake / stopshake 控制)
|
||
public private(set) var canShake: Bool = false
|
||
|
||
/// 摇一摇音效开关(SwitchShake 控制)
|
||
public private(set) var canVoice: Bool = true
|
||
|
||
private weak var bridge: (any BridgeProtocol)?
|
||
|
||
public init() {}
|
||
|
||
public func register(on bridge: any BridgeProtocol) {
|
||
self.bridge = bridge
|
||
|
||
// 【7】startshake — 开摇
|
||
// 入参忽略 → canshake = YES
|
||
// cb:"startshake from accreditlogin"(字面字符串带后缀,沿用历史;
|
||
// H5 不依赖此值,但严格拷贝保留契约等价)
|
||
bridge.register("startshake") { [weak self] _, callback in
|
||
Task { @MainActor in self?.canShake = true }
|
||
callback?(.string("startshake from accreditlogin"))
|
||
}
|
||
|
||
// 【8】stopshake — 关摇
|
||
// 入参忽略 → canshake = NO;cb 同 startshake
|
||
bridge.register("stopshake") { [weak self] _, callback in
|
||
Task { @MainActor in self?.canShake = false }
|
||
callback?(.string("startshake from accreditlogin"))
|
||
}
|
||
|
||
// 【9】SwitchShake — 摇一摇音效开关
|
||
// 入参 int 1 → canvoice = YES,其它 → canvoice = NO
|
||
// cb:"SwitchShake"
|
||
bridge.register("SwitchShake") { [weak self] data, callback in
|
||
let on = (data?.asInt ?? 0) == 1
|
||
Task { @MainActor in self?.canVoice = on }
|
||
callback?(.string("SwitchShake"))
|
||
}
|
||
}
|
||
|
||
/// 由 WebContainerViewController.motionEnded(_:with:) 转发调用。
|
||
/// 触发反向 callback `shakeEnd`(仅 `canshake=YES` 时)。
|
||
public func handleMotionEnded(_ motion: UIEvent.EventSubtype) {
|
||
guard motion == .motionShake, canShake else { return }
|
||
// Phase 2.3 暂不播放摇一摇音效(依赖 AudioKit / shake_sound_male.mp3,
|
||
// 留待 Phase 3 AudioKit 完整时补;canVoice 状态保留供后续使用)
|
||
bridge?.call("shakeEnd", data: nil, callback: nil)
|
||
}
|
||
}
|