IPA 升级 itms-services 路径补主动 suspend:用户回桌面 + 看到下载进度
【背景修正】
上个 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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
befe205bf9
commit
960adc5491
@@ -554,26 +554,44 @@ public final class WebContainerViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func showIPAUpgradeAlert(downloadURL: String) {
|
private func showIPAUpgradeAlert(downloadURL: String) {
|
||||||
// 行为目标:点「确定」直接弹系统安装窗(不跳 Safari),用户点「安装」后
|
// 行为目标:点「确定」直接弹系统安装窗 + App 自动切后台(用户回到桌面),
|
||||||
// App 自然切后台、用户看到桌面图标下载进度,App 进程留在内存,无需主动 exit。
|
// 用户在桌面上点「安装」即可看到 App 图标下载进度,App 进程留在内存不退出。
|
||||||
//
|
//
|
||||||
// 实现依赖:URL 必须是 iOS 可直接接管的安装协议(itms-services 或 manifest plist 直链)。
|
// iOS 实际行为:调 itms-services 时系统弹安装确认窗,但本 App 仍处于 inactive
|
||||||
// 详见 makeInstallURL 注释。如果远端配的是 https 网页链接,仍会跳 Safari
|
// 状态(弹窗是模态覆盖在 App 上),用户点「安装」后弹窗 dismiss、App 回到 active
|
||||||
// 给用户看引导页 —— 这是预期外的情形,需后台把 appDownload 改成 itms-services
|
// —— 系统**不会主动**切后台。要让用户回桌面,需要本 App 主动调私有 selector
|
||||||
// 或 .plist 直链才能跳过 Safari 中转。
|
// `suspend` 把自己切后台(等价于用户按 Home / 上滑)。
|
||||||
|
// 本项目企业签 / 渠道分发,不上架 AppStore,可以用私有 API。
|
||||||
|
//
|
||||||
|
// URL 形态:itms-services 直链 / .plist 直链 / 其它(Safari)—— 详见 makeInstallURL。
|
||||||
|
// Safari 路径不调 suspend:用户已经在 Safari,suspend 反而破坏 Safari 引导页体验。
|
||||||
let alert = UIAlertController(
|
let alert = UIAlertController(
|
||||||
title: "需要升级",
|
title: "需要升级",
|
||||||
message: "检测到新版本,点击「确定」开始升级,系统会自动下载并安装。",
|
message: "检测到新版本,点击「确定」开始升级,系统会自动下载并安装。",
|
||||||
preferredStyle: .alert
|
preferredStyle: .alert
|
||||||
)
|
)
|
||||||
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
|
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
|
||||||
if let url = Self.makeInstallURL(from: downloadURL) {
|
guard let url = Self.makeInstallURL(from: downloadURL) else { return }
|
||||||
UIApplication.shared.open(url)
|
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)
|
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 中转。
|
/// 把远端 `appDownload` 规范化为 iOS 系统可直接接管的安装协议,避免跳 Safari 中转。
|
||||||
///
|
///
|
||||||
/// 三种 raw URL 形态:
|
/// 三种 raw URL 形态:
|
||||||
|
|||||||
Reference in New Issue
Block a user