Files
joywayer c398a8dd58 3.C:录音组件落地(VoiceRecorder + Overlay + Presenter + Window 子类)
完整支撑 Phase 3.E prepareaudio handler 的录音子链路:用户按住 H5 麦克风按钮
→ Native 录音 → 松手停止 → 触发完成回调。

## 新增模块

- `RecordingAwareWindow`:UIWindow 子类,提供 touch 旁路监听通道
  (static touchObserver closure)。默认行为与 UIWindow 完全一致,仅
  RecordingPresenter 注册 observer 后才触发额外行为。daoqi msext
  CustomWindow.m + nScreenTouch 通知同款思路,改用 closure 注入
- `VoiceRecorder`:AVAudioRecorder + AudioSession + 0.1s 计时器封装。
  ObservableObject + @Published(暴露 averagePower / elapsed 给 SwiftUI)。
  录音参数 8kHz/16bit/mono PCM 严格对齐 daoqi(AMR-NB 编码硬要求)。
  60s 上限对齐 daoqi kDefaultMaxRecordTime
- `RecordingOverlay`:SwiftUI 浮层(UI 内部实现,按 CLAUDE.md「原则 B 原生
  内部自由」与 daoqi 不一致)。半透明黑底胶囊 + SF Symbols 麦克风 + 音量
  光晕动画 + 倒计时(剩 10s 显示秒数,对齐 daoqi UI 逻辑)
- `RecordingPresenter`:组装入口。挂载 overlay 到 hostVC 全屏覆盖 +
  注册 touchObserver。提供 `start(on:fileName:onFinish:)` 单一 API +
  `cancelActive()` 强制取消

## SceneDelegate 改动

UIWindow → RecordingAwareWindow 实例化。变量类型仍为 UIWindow?
(多态),sendEvent 走子类版本。零行为变化(observer 未注册时)。

## 为何必须走 UIWindow 子类

H5 麦克风按钮按下触发 prepareaudio → 录音浮层在按住状态下中途弹出。
iOS touch 事件流是 view hit-test 链,**in-flight touch 不传给中途加入
的 view**,浮层 view 自身收不到 touchesEnded,用户松手时检测不到。

唯一可靠路径是 UIWindow.sendEvent 旁路截获(UIWindow 接收所有 touch
event 无论 in-flight 还是新 touch)。daoqi msext 也是这条路。

## Combine 引入

VoiceRecorder 用 ObservableObject + @Published 给 SwiftUI 数据绑定,
需要 import Combine。CLAUDE.md「避免 Combine」精神是禁用 Publisher 链
替代 async/await,不是禁止 ObservableObject(iOS 15.6 无 @Observable
宏可替代)。已在 VoiceRecorder.swift 注释说明。

## 验证

BuildProject 通过 0 错误,全部新文件 XcodeRefreshCodeIssuesInFile 0 诊断。
2026-06-24 01:29:04 +08:00

46 lines
1.9 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// SceneDelegate.swift
// ylgamehall
//
import UIKit
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
// 使用 RecordingAwareWindowUIWindow 子类)以便录音浮层旁路监听 touch
// 事件。默认行为与 UIWindow 完全一致,仅在 RecordingPresenter 注册
// touchObserver 后才有额外行为。详见 Source/Audio/RecordingAwareWindow.swift
let window = RecordingAwareWindow(windowScene: windowScene)
// 用 UINavigationController 承载大厅,便于 Phase 6 push 子游戏 / Phase 7 push 弹层。
// 栈深约束 ≤ 3Design §2.4.3),由 AppCoordinator 在 push 处守门。
let lobby = WebContainerViewController()
let nav = UINavigationController(rootViewController: lobby)
nav.setNavigationBarHidden(true, animated: false)
nav.interactivePopGestureRecognizer?.isEnabled = false
AppCoordinator.shared.navigationController = nav
window.rootViewController = nav
self.window = window
window.makeKeyAndVisible()
}
/// 第三方 SDK 回调入口(微信授权 / 微信分享回包等)。
/// 顺序硬约束:QQ → 微信(msext AppDelegate.m 同款);QQ 走 URL Scheme 不依赖 SDK
/// 这里直接调微信 handler — 未来若 QQ 升级为 SDK 路径则在前面插一条。
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for ctx in URLContexts {
// Phase 4.E 微信回调(canImport 守卫:未链接时 no-op
if WeChatSDK.handleOpenURL(ctx.url) { continue }
// 未来:抖音 / 其它 SDK 回调依次判
}
}
}