From 960adc5491515fac4f307c10789a888470b95ff0 Mon Sep 17 00:00:00 2001 From: joywayer Date: Sat, 27 Jun 2026 22:12:59 +0800 Subject: [PATCH] =?UTF-8?q?IPA=20=E5=8D=87=E7=BA=A7=20itms-services=20?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E8=A1=A5=E4=B8=BB=E5=8A=A8=20suspend?= =?UTF-8?q?=EF=BC=9A=E7=94=A8=E6=88=B7=E5=9B=9E=E6=A1=8C=E9=9D=A2=20+=20?= =?UTF-8?q?=E7=9C=8B=E5=88=B0=E4=B8=8B=E8=BD=BD=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【背景修正】 上个 commit (befe205) commit msg 里说"iOS 调 itms-services 时会自动把 App 切后台" 不准确 —— 实测系统弹窗只是模态覆盖在本 App 上,用户点「安装」后 App 回到 active, 桌面图标下载进度需要用户手动按 Home 才能看到。 【方案】 调 UIApplication.open(itms-services URL) 后,0.3s 用私有 selector "suspend" 把 App 主动切后台(等价按 Home / 上滑): - 用户立即看到桌面 + 系统安装确认窗(弹窗是系统级,覆盖在桌面上) - 用户点「安装」→ 弹窗 dismiss → 用户已经在桌面 → 看到 App 图标下载进度 - App 进程留在内存,IPA 替换时由 iOS 自动结束 【私有 API 风险评估】 suspend selector 是 iOS 私有 API,AppStore 审核会拒。本项目企业签 / 渠道分发, 不上架 AppStore,可以用 (CLAUDE.md 第 6 节稳定>一切原则下接受)。 UIControl().sendAction 比 perform() 更安全:selector 不响应时静默忽略,不 crash。 【仅 itms-services 路径触发 suspend】 Safari 路径(普通 https URL)不调 suspend:用户已经在 Safari,主动切后台反而 让 Safari 也消失,糟糕的 UX。 BuildProject 通过。 Co-Authored-By: Claude Opus 4.7 --- .../WebView/WebContainerViewController.swift | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/ylgamehall/Source/WebView/WebContainerViewController.swift b/ylgamehall/Source/WebView/WebContainerViewController.swift index 60c3fd9..e0b7e21 100644 --- a/ylgamehall/Source/WebView/WebContainerViewController.swift +++ b/ylgamehall/Source/WebView/WebContainerViewController.swift @@ -554,26 +554,44 @@ public final class WebContainerViewController: UIViewController { } private func showIPAUpgradeAlert(downloadURL: String) { - // 行为目标:点「确定」直接弹系统安装窗(不跳 Safari),用户点「安装」后 - // App 自然切后台、用户看到桌面图标下载进度,App 进程留在内存,无需主动 exit。 + // 行为目标:点「确定」直接弹系统安装窗 + App 自动切后台(用户回到桌面), + // 用户在桌面上点「安装」即可看到 App 图标下载进度,App 进程留在内存不退出。 // - // 实现依赖:URL 必须是 iOS 可直接接管的安装协议(itms-services 或 manifest plist 直链)。 - // 详见 makeInstallURL 注释。如果远端配的是 https 网页链接,仍会跳 Safari - // 给用户看引导页 —— 这是预期外的情形,需后台把 appDownload 改成 itms-services - // 或 .plist 直链才能跳过 Safari 中转。 + // iOS 实际行为:调 itms-services 时系统弹安装确认窗,但本 App 仍处于 inactive + // 状态(弹窗是模态覆盖在 App 上),用户点「安装」后弹窗 dismiss、App 回到 active + // —— 系统**不会主动**切后台。要让用户回桌面,需要本 App 主动调私有 selector + // `suspend` 把自己切后台(等价于用户按 Home / 上滑)。 + // 本项目企业签 / 渠道分发,不上架 AppStore,可以用私有 API。 + // + // URL 形态:itms-services 直链 / .plist 直链 / 其它(Safari)—— 详见 makeInstallURL。 + // Safari 路径不调 suspend:用户已经在 Safari,suspend 反而破坏 Safari 引导页体验。 let alert = UIAlertController( title: "需要升级", message: "检测到新版本,点击「确定」开始升级,系统会自动下载并安装。", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in - if let url = Self.makeInstallURL(from: downloadURL) { - UIApplication.shared.open(url) + guard let url = Self.makeInstallURL(from: downloadURL) else { return } + UIApplication.shared.open(url) + if url.scheme?.lowercased() == "itms-services" { + // 0.3s 等系统接收 itms-services URL(确认弹窗 schedule 完成), + // 然后把 App 切后台。系统弹窗仍会覆盖在桌面上正常显示。 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + Self.suspendAppToHomeScreen() + } } }) present(alert, animated: true) } + /// 把本 App 切到后台(用户看到桌面),App 进程留在内存。 + /// 私有 selector `suspend` 等价于按 Home / 上滑回桌面的系统行为。 + /// `UIControl.sendAction` 内部会处理 selector 不响应的情况,比 `perform()` 安全。 + private static func suspendAppToHomeScreen() { + let sel = NSSelectorFromString("suspend") + UIControl().sendAction(sel, to: UIApplication.shared, for: nil) + } + /// 把远端 `appDownload` 规范化为 iOS 系统可直接接管的安装协议,避免跳 Safari 中转。 /// /// 三种 raw URL 形态: