Phase 7 完整实现:OpenurlTitleData 弹层(OverlayViewController + 3 个 settings)
- AppCoordinator 扩 OverlayRequest + lastOverlayAt(3s 节流 / 栈深 < 3 守门) +
showOverlay / popOverlay(data==nil 仅 pop 即 finishweb 路径;
data!=nil 同时发 .subGameDidReturn 即 backgameData 路径)
- 新增 Source/WebView/OverlayViewController.swift:独立 WKWebView,
WKWebViewConfiguration.websiteDataStore = .nonPersistent() 隔离 Cookie / 缓存;
.atDocumentEnd 注入 polyfill:window.settings = {browser/finishweb/backgameData}
→ webkit.messageHandlers.<name>(与 WVJB 不同 channel 可同名共存);
ScriptMessageProxy weak target 切断 retain cycle
- OpenurlTitleDataHandler 升级为真实 push:解 url / "title "(末尾空格契约)/
data / orientation → AppCoordinator.showOverlay,cb 字面始终回 "OpenurlTitleData"
- SubGameViewController.setupExternalSubscriptions 也订阅 .subGameDidReturn
(overlay 可从子游戏 push,pop 回子游戏时需要 callback 子游戏 H5 的 getWebdata;
与 viewWillAppear/Disappear 生命周期对齐,栈顶才挂钩避免双发)
- orientation 字段保留契约但 landscape-only app 暂不实施旋转(msext 旋转 transform
在我们项目里无意义,记注释,如有 H5 反馈再补)
- Plan §5.7.1-7.7 / §8 进度已勾选
至此 SwitchOverGameData → push 子游戏 / OpenurlTitleData → push 弹层 / backgameData ↔
finishweb → pop + getWebdata 反向闭环全部接通。栈深永远 ≤ 3。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
5b67e414b3
commit
0427bb9542
+23
-12
@@ -634,13 +634,23 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
|
||||
|
||||
#### 任务清单
|
||||
|
||||
- [ ] **7.1** `Source/Containers/OverlayViewController.swift`:继承 WebContainer,但 WKWebsiteDataStore 用 `.nonPersistent()` 隔离
|
||||
- [ ] **7.2** `Source/WebView/OverlayBridge.swift` polyfill JS:`window.settings = {backgameData, browser, finishweb}` → `webkit.messageHandlers.*`
|
||||
- [ ] **7.3** `WKScriptMessageHandler` 注册三 handler:`overlayBackgameData` / `overlayBrowser` / `overlayFinishweb`
|
||||
- [ ] **7.4** `OpenurlTitleData` handler(大厅 + 子游戏都注册):解参(注意 `"title "` 末尾空格契约) → AppCoordinator.showOverlay → 3 秒节流
|
||||
- [ ] **7.5** Overlay backgameData → 发 `.subGameDidReturn` → 上层 H5 收 `getWebdata`
|
||||
- [ ] **7.6** Overlay `finishweb` → coordinator.popOverlay
|
||||
- [ ] **7.7** Overlay `browser` → 系统 Safari 打开
|
||||
- [x] **7.1** `Source/WebView/OverlayViewController.swift`:独立 WKWebView,`WKWebViewConfiguration.websiteDataStore = .nonPersistent()`
|
||||
隔离弹层 Cookie / 缓存(不污染大厅 / 子游戏会话)
|
||||
- [x] **7.2** polyfill JS 内联(不抽独立 .js 文件):`.atDocumentEnd` 注入
|
||||
`window.settings={browser,finishweb,backgameData}` → `webkit.messageHandlers.*`
|
||||
(msext threeView.m:223-241 在 webViewDidFinishLoad 注入 `context[@"settings"]=jo` 等价时机)
|
||||
- [x] **7.3** `WKScriptMessageHandler` 通过 `ScriptMessageProxy`(weak target 切断 retain cycle)
|
||||
注册 3 handler:`browser` / `finishweb` / `backgameData`(messageHandler 与 WVJB 不同 channel,
|
||||
可与大厅 WVJB 同名共存不冲突)
|
||||
- [x] **7.4** `OpenurlTitleData` handler(大厅 + 子游戏都注册):解参(`"title "` 末尾空格契约硬约束) →
|
||||
`AppCoordinator.showOverlay` → 3 秒节流由 Coordinator 守门(msext first_Time 等价)
|
||||
- [x] **7.5** Overlay backgameData → `AppCoordinator.popOverlay(returningData:)` → 发 `.subGameDidReturn`
|
||||
→ 父层(大厅 / 子游戏)观察后 callback H5 `getWebdata`
|
||||
- [x] **7.6** Overlay `finishweb` → `popOverlay(returningData: nil)` 仅 pop 不发通知
|
||||
- [x] **7.7** Overlay `browser` → `UIApplication.shared.open` Safari 外链
|
||||
- [x] **额外**:SubGameViewController.setupExternalSubscriptions 也订阅 `.subGameDidReturn`
|
||||
(overlay 可从子游戏 push,pop 回子游戏时需要 callback 子游戏 H5 的 getWebdata;
|
||||
与大厅 / 子游戏的 viewWillAppear/Disappear 生命周期对齐,栈顶才挂钩避免双发)
|
||||
|
||||
#### 验收
|
||||
|
||||
@@ -942,11 +952,12 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
|
||||
- [x] 6.7 SubGameDownloader(actor,URLSession + ZIPFoundation + staging + 原子 rename)
|
||||
|
||||
### Phase 7 弹层
|
||||
- [ ] 7.1 OverlayViewController + 私有 dataStore
|
||||
- [ ] 7.2 window.settings polyfill
|
||||
- [ ] 7.3 3 个 WKScriptMessageHandler
|
||||
- [ ] 7.4 OpenurlTitleData handler("title " 末尾空格)
|
||||
- [ ] 7.5 Overlay backgameData
|
||||
- [x] 7.1 OverlayViewController + 私有 dataStore(.nonPersistent)
|
||||
- [x] 7.2 window.settings polyfill(.atDocumentEnd 注入)
|
||||
- [x] 7.3 3 个 WKScriptMessageHandler(ScriptMessageProxy 切断 retain cycle)
|
||||
- [x] 7.4 OpenurlTitleData handler("title " 末尾空格 + 3s 节流由 Coordinator 守门)
|
||||
- [x] 7.5 Overlay backgameData(→ popOverlay 发 .subGameDidReturn → 父层 getWebdata)
|
||||
- [x] 额外 SubGameViewController 也订阅 .subGameDidReturn(覆盖 overlay→sub-game 返回)
|
||||
- [ ] 7.6 Overlay finishweb
|
||||
- [ ] 7.7 Overlay browser
|
||||
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
// OpenurlTitleDataHandler.swift
|
||||
// ylgamehall
|
||||
//
|
||||
// H5 → Native handler stub:OpenurlTitleData — 打开内嵌 WebView 弹层(threeView)
|
||||
// H5 → Native handler:OpenurlTitleData — 打开内嵌 WebView 弹层(msext threeView 等价)。
|
||||
// 契约:docs/H5-Native-Contract.md §3.1 [15]
|
||||
//
|
||||
// Phase 7 完整实现需要 OverlayViewController + window.settings polyfill
|
||||
// + 3 秒节流。当前为 stub(仅 cb 维持契约),让 H5 调时不报 "no handler"。
|
||||
// ⚠️ 入参 key "title " 末尾有空格(msext 历史契约硬约束):
|
||||
// 必须 `data["title "]?.asString` 而不是 `data["title"]?.asString`,
|
||||
// H5 端 callHandler 时也是 `{"title ": "..."}`(带空格的字符串字面)
|
||||
//
|
||||
// ⚠️ 注意入参 key "title " 末尾有空格(msext 历史契约硬约束),完整实现时
|
||||
// 必须 obj["title "]?.asString 而不是 obj["title"]?.asString。
|
||||
// 与 msext NewRootVC.m / gameController.m 等价:
|
||||
// - 大厅 + 子游戏页都注册(msext 两处都有),用户在哪一页调都能打开弹层
|
||||
// - 3 秒节流由 AppCoordinator.showOverlay 守门(msext first_Time 等价)
|
||||
// - callback 字面始终回 "OpenurlTitleData",无论节流是否命中(msext 行为)
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -20,8 +23,25 @@ public enum OpenurlTitleDataHandler {
|
||||
// 【15】OpenurlTitleData
|
||||
// 入参 url / "title "(末尾空格!) / data / orientation
|
||||
// cb: "OpenurlTitleData"
|
||||
// 完整实现需 push OverlayViewController + 3 秒节流(first_Time 标志)
|
||||
bridge.register("OpenurlTitleData") { _, callback in
|
||||
bridge.register("OpenurlTitleData") { data, callback in
|
||||
await MainActor.run {
|
||||
// ⚠️ "title " 末尾空格不可改;orientation 在 landscape-only app 中保留契约字段
|
||||
let url = data?["url"]?.asString ?? ""
|
||||
let title = data?["title "]?.asString ?? ""
|
||||
let webData = data?["data"]?.asString ?? ""
|
||||
let orientation = data?["orientation"]?.asInt
|
||||
?? Int(data?["orientation"]?.asString ?? "")
|
||||
?? 0
|
||||
|
||||
let request = OverlayRequest(
|
||||
url: url,
|
||||
title: title,
|
||||
webData: webData,
|
||||
orientation: orientation
|
||||
)
|
||||
AppCoordinator.shared.showOverlay(request)
|
||||
}
|
||||
// msext 行为:无论节流命中 / 入参缺失,都回 cb,避免 H5 等待
|
||||
callback?(.string("OpenurlTitleData"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
//
|
||||
// 与 msext 一对一行为:
|
||||
// - SwitchOverGameData 节流:2s 内不重复触发
|
||||
// - subGameDidReturn 通知:子游戏 pop 时发,大厅监听并 callback getWebdata
|
||||
// - OpenurlTitleData 节流:3s 内不重复触发(msext first_Time 等价)
|
||||
// - subGameDidReturn 通知:子游戏 / 弹层 pop 时发,父层(大厅 / 子游戏)监听并 callback getWebdata
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -30,9 +31,26 @@ public struct SubGameRequest: Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// 子游戏 pop 回大厅通知(msext .subGameDidReturn 等价)
|
||||
/// 弹层 push 请求(从 H5 OpenurlTitleData 入参解析)
|
||||
public struct OverlayRequest: Sendable {
|
||||
public let url: String // 待加载 HTTP URL
|
||||
public let title: String // 弹层标题(msext 历史:nav 默认隐藏,title 仅信息性)
|
||||
public let webData: String // data:业务数据透传给 threeView 内 H5
|
||||
public let orientation: Int // 0=竖屏 / 1=横屏(landscape-only app 忽略,预留契约字段)
|
||||
|
||||
public init(url: String, title: String, webData: String, orientation: Int) {
|
||||
self.url = url
|
||||
self.title = title
|
||||
self.webData = webData
|
||||
self.orientation = orientation
|
||||
}
|
||||
}
|
||||
|
||||
/// 子容器(子游戏 / 弹层)pop 回父层通知。
|
||||
/// msext 同款 "backgameDatatwo":threeView 和 gameController 共用一个名字,
|
||||
/// 父层(大厅 / 子游戏)监听后转 `getWebdata` 反向 callback。
|
||||
public extension Notification.Name {
|
||||
/// 子游戏 pop 返回时发出,userInfo["data"] = 子游戏 backgameData 入参字符串
|
||||
/// 子游戏 / 弹层 pop 返回时发出,userInfo["data"] = 调 backgameData 时传入的字符串
|
||||
static let subGameDidReturn = Notification.Name("ylgamehall.subGameDidReturn")
|
||||
}
|
||||
|
||||
@@ -41,8 +59,10 @@ public final class AppCoordinator {
|
||||
|
||||
public static let shared = AppCoordinator()
|
||||
|
||||
/// 节流:2 秒内连续 SwitchOverGameData 视为重复(msext first_Time 等价)
|
||||
/// SwitchOverGameData 节流:2 秒内连续触发视为重复(msext first_Time 等价)
|
||||
private var lastSwitchAt: Date?
|
||||
/// OpenurlTitleData 节流:3 秒内连续触发视为重复(契约 §3.1 [15] first_Time 等价)
|
||||
private var lastOverlayAt: Date?
|
||||
|
||||
/// 当前可见 WebContainer(大厅)— 用于 push 子游戏
|
||||
public weak var navigationController: UINavigationController?
|
||||
@@ -80,4 +100,39 @@ public final class AppCoordinator {
|
||||
userInfo: ["data": data]
|
||||
)
|
||||
}
|
||||
|
||||
/// 大厅 / 子游戏触发 OpenurlTitleData。节流命中或栈深已满(3 层)时不触发。
|
||||
/// 返回 true 表示 push 成功,false 表示被忽略。
|
||||
@discardableResult
|
||||
public func showOverlay(_ request: OverlayRequest) -> Bool {
|
||||
// 节流:3s 内重复调用直接吞(msext first_Time 等价)
|
||||
if let last = lastOverlayAt, Date().timeIntervalSince(last) < 3 {
|
||||
return false
|
||||
}
|
||||
// 栈深约束:最多 Lobby + SubGame + Overlay = 3 层
|
||||
guard let nav = navigationController,
|
||||
nav.viewControllers.count < 3
|
||||
else {
|
||||
return false
|
||||
}
|
||||
lastOverlayAt = Date()
|
||||
|
||||
let overlay = OverlayViewController(request: request)
|
||||
nav.pushViewController(overlay, animated: true)
|
||||
return true
|
||||
}
|
||||
|
||||
/// 弹层 pop 回父层(大厅 / 子游戏)。
|
||||
/// - data == nil:`settings.finishweb()` 路径,仅 pop,不发 backgameDatatwo
|
||||
/// - data != nil:`settings.backgameData(data)` 路径,pop + 发 .subGameDidReturn 通知
|
||||
public func popOverlay(returningData data: String?) {
|
||||
navigationController?.popViewController(animated: true)
|
||||
if let data {
|
||||
NotificationCenter.default.post(
|
||||
name: .subGameDidReturn,
|
||||
object: nil,
|
||||
userInfo: ["data": data]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// OverlayViewController.swift
|
||||
// ylgamehall
|
||||
//
|
||||
// 弹层(msext threeView 等价):WKWebView 加载外部 HTTP 页面,
|
||||
// H5 通过 `window.settings.{browser,finishweb,backgameData}` 三接口与原生交互。
|
||||
//
|
||||
// 契约:docs/H5-Native-Contract.md §3.4 弹层桥
|
||||
// Design:§3.4.2 / §2.4.3 栈深 ≤ 3
|
||||
//
|
||||
// 与 msext threeView.m 的差异:
|
||||
// - msext 用 UIWebView + JSExport (Bridgetwo);新外壳用 WKWebView + WKScriptMessageHandler,
|
||||
// 在 .atDocumentEnd 注入 `window.settings = {...}` polyfill 桥到 messageHandlers
|
||||
// - msext orientation=1 时 CGAffineTransformMakeRotation(±π/2) 旋转 webView;
|
||||
// 本项目 landscape-locked,orientation 字段保留但不主动旋转(如有 H5 反馈需要再补)
|
||||
// - msext 用全局 sharedHTTPCookieStorage;本项目用 WKWebsiteDataStore.nonPersistent()
|
||||
// 隔离弹层 Cookie / 缓存,避免污染主业务态(Design §3.4.2 风险条目)
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
public final class OverlayViewController: UIViewController {
|
||||
|
||||
// MARK: - 入参
|
||||
|
||||
private let request: OverlayRequest
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private var webView: WKWebView!
|
||||
|
||||
/// WKScriptMessageHandler 名字(与 H5 端 polyfill 内 `webkit.messageHandlers.<name>` 一致)。
|
||||
/// 弹层 WKWebView 与大厅 WVJB 在不同 channel,可以与 WVJB 同名 handler 共存不冲突。
|
||||
/// fileprivate 让同文件下的 ScriptMessageProxy 可用 `OverlayViewController.MessageName(rawValue:)`
|
||||
fileprivate enum MessageName: String, CaseIterable {
|
||||
case browser
|
||||
case finishweb
|
||||
case backgameData
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(request: OverlayRequest) {
|
||||
self.request = request
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("OverlayViewController requires OverlayRequest")
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .black
|
||||
title = request.title
|
||||
|
||||
setupWebView()
|
||||
loadRequest()
|
||||
}
|
||||
|
||||
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { .landscape }
|
||||
public override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { .landscapeRight }
|
||||
public override var prefersStatusBarHidden: Bool { false }
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private func setupWebView() {
|
||||
let config = WKWebViewConfiguration()
|
||||
// 关键:弹层 Cookie / 缓存隔离,第三方外链不污染大厅 / 子游戏的 H5 会话
|
||||
config.websiteDataStore = .nonPersistent()
|
||||
config.defaultWebpagePreferences.allowsContentJavaScript = true
|
||||
config.preferences.javaScriptCanOpenWindowsAutomatically = false
|
||||
config.allowsInlineMediaPlayback = true
|
||||
|
||||
// .atDocumentEnd 注入 polyfill:与 msext threeView.webViewDidFinishLoad 等价时机
|
||||
// 用 documentEnd 而不是 documentStart,是为了让外链页自带的 JS 框架先初始化,
|
||||
// 再覆盖 / 创建 window.settings — msext 在 didFinishLoad 才注入,行为等价。
|
||||
let polyfill = MessageName.allCases.map { name in
|
||||
switch name {
|
||||
case .browser:
|
||||
return "browser:function(u){webkit.messageHandlers.browser.postMessage(String(u||''));}"
|
||||
case .finishweb:
|
||||
return "finishweb:function(){webkit.messageHandlers.finishweb.postMessage('');}"
|
||||
case .backgameData:
|
||||
return "backgameData:function(d){webkit.messageHandlers.backgameData.postMessage(String(d==null?'':d));}"
|
||||
}
|
||||
}.joined(separator: ",")
|
||||
let source = "window.settings=window.settings||{};Object.assign(window.settings,{\(polyfill)});"
|
||||
let userScript = WKUserScript(
|
||||
source: source,
|
||||
injectionTime: .atDocumentEnd,
|
||||
forMainFrameOnly: true
|
||||
)
|
||||
config.userContentController.addUserScript(userScript)
|
||||
|
||||
// 3 个 MessageHandler 名字与 polyfill 内的 messageHandlers.<name> 一致
|
||||
for name in MessageName.allCases {
|
||||
config.userContentController.add(
|
||||
ScriptMessageProxy(target: self),
|
||||
name: name.rawValue
|
||||
)
|
||||
}
|
||||
|
||||
webView = WKWebView(frame: .zero, configuration: config)
|
||||
webView.scrollView.bounces = false
|
||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(webView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
webView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func loadRequest() {
|
||||
guard let url = URL(string: request.url) else {
|
||||
showFatalAlert(message: "URL 无效:\(request.url)")
|
||||
return
|
||||
}
|
||||
webView.load(URLRequest(url: url))
|
||||
}
|
||||
|
||||
// MARK: - Message handling
|
||||
|
||||
fileprivate func handleMessage(_ name: MessageName, body: Any) {
|
||||
switch name {
|
||||
case .browser:
|
||||
// 系统 Safari 打开(msext threeView.browser: 等价)
|
||||
let raw = (body as? String) ?? ""
|
||||
guard !raw.isEmpty,
|
||||
let target = URL(string: raw)
|
||||
?? raw.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
|
||||
.flatMap(URL.init(string:))
|
||||
else { return }
|
||||
Task { @MainActor in
|
||||
_ = await UIApplication.shared.open(target)
|
||||
}
|
||||
|
||||
case .finishweb:
|
||||
// 仅 pop,不带数据(settings.finishweb())
|
||||
AppCoordinator.shared.popOverlay(returningData: nil)
|
||||
|
||||
case .backgameData:
|
||||
// pop + 发 .subGameDidReturn,父层(大厅 / 子游戏)观察后回调 H5 getWebdata
|
||||
let payload = serialize(body)
|
||||
AppCoordinator.shared.popOverlay(returningData: payload)
|
||||
}
|
||||
}
|
||||
|
||||
/// 兼容 string / dict / array:dict/array 走 JSON 序列化透传给父层 H5
|
||||
private func serialize(_ body: Any) -> String {
|
||||
if let s = body as? String { return s }
|
||||
if JSONSerialization.isValidJSONObject(body),
|
||||
let bytes = try? JSONSerialization.data(withJSONObject: body, options: [.fragmentsAllowed]),
|
||||
let s = String(data: bytes, encoding: .utf8) {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private func showFatalAlert(message: String) {
|
||||
let alert = UIAlertController(
|
||||
title: "加载失败",
|
||||
message: message,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "返回", style: .default) { [weak self] _ in
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WKScriptMessageHandler proxy
|
||||
//
|
||||
// WKScriptMessageHandler 协议默认 retain 持有者,会跟 WebView 互相强引用导致 VC 不释放。
|
||||
// 用一个 weak 持有 target 的 proxy 切断循环引用 — msext 时代手动 MRC release 等价做法。
|
||||
|
||||
private final class ScriptMessageProxy: NSObject, WKScriptMessageHandler {
|
||||
private weak var target: OverlayViewController?
|
||||
|
||||
init(target: OverlayViewController) {
|
||||
self.target = target
|
||||
super.init()
|
||||
}
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController,
|
||||
didReceive message: WKScriptMessage) {
|
||||
guard let target,
|
||||
let name = OverlayViewController.MessageName(rawValue: message.name)
|
||||
else { return }
|
||||
target.handleMessage(name, body: message.body)
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,10 @@ public final class SubGameViewController: UIViewController {
|
||||
|
||||
private let shakeHandler = ShakeHandler()
|
||||
|
||||
// MARK: - .subGameDidReturn 观察者(overlay pop 回子游戏时反向 callback getWebdata)
|
||||
|
||||
private var subGameReturnObserver: NSObjectProtocol?
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(request: SubGameRequest) {
|
||||
@@ -239,6 +243,19 @@ public final class SubGameViewController: UIViewController {
|
||||
AppLifecycleObserver.shared.onForeground = {
|
||||
bridge.call("appservice", data: .string("2"), callback: nil)
|
||||
}
|
||||
|
||||
// overlay(Phase 7)pop 回子游戏时反向 callback getWebdata。挂在子游戏栈顶
|
||||
// 期间生效;overlay push 上来时 viewWillDisappear → teardown 自动解绑,避免双发。
|
||||
subGameReturnObserver = NotificationCenter.default.addObserver(
|
||||
forName: .subGameDidReturn,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { note in
|
||||
let data = (note.userInfo?["data"] as? String) ?? ""
|
||||
MainActor.assumeIsolated {
|
||||
bridge.call("getWebdata", data: .string(data), callback: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func teardownExternalSubscriptions() {
|
||||
@@ -246,6 +263,11 @@ public final class SubGameViewController: UIViewController {
|
||||
NetworkMonitor.shared.onChange = nil
|
||||
AppLifecycleObserver.shared.onBackground = nil
|
||||
AppLifecycleObserver.shared.onForeground = nil
|
||||
|
||||
if let token = subGameReturnObserver {
|
||||
NotificationCenter.default.removeObserver(token)
|
||||
subGameReturnObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Error alert
|
||||
|
||||
Reference in New Issue
Block a user