关于 capacitor-uiscene-migration
用于审计并将 Capacitor iOS 项目从 AppDelegate 生命周期迁移到 UIScene,以满足 Xcode 27 要求。涵盖迁移状态、自定义生命周期与 URL 处理,以及构建和运行检查。
主要功能
- 01判定 UIScene 迁移状态
- 02审查生命周期与 URL 处理代码
- 03指导 AppDelegate 和 SceneDelegate 修改
- 04检查插件与 SDK 的生命周期问题
- 05验证构建和应用行为
适用场景
- 01让 Capacitor 8.4 或更早版本适配 Xcode 27
- 02处理 cap migrate 的部分迁移结果
- 03采用场景生命周期后保留深层链接
- 04检查插件的 UIScene 兼容性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cap-go/ capgo-skills --skill capacitor-uiscene-migration安装后在 Claude Code 中输入/capacitor-uiscene-migration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.jsonreferences/audit-patterns.mdpbxproj-registration.mdplugin-audit.mdscene-delegate-template.md详细介绍
Capacitor UIScene 迁移(8.4 -> 8.5)
Capacitor 8.5 将 iOS 模板迁移到 UIScene 生命周期,因为 Xcode 27 要求采用该生命周期。此版本作为一个有破坏性变更的小版本发布,仅影响 iOS。8.5 核心仍支持 AppDelegate 路径,因此仅升级依赖不会导致应用出问题;应用项目必须采用场景,才能使用 Xcode 27 构建(也必须在升级到 Capacitor 9 之前完成)。
规范来源:https://capacitorjs.com/docs/updating/8-5
适用场景
满足以下情况时使用:
- 应用使用
@capacitor/ios8.0-8.4,或使用 8.5 但Info.plist中没有UIApplicationSceneManifest - 使用 Xcode 27 构建或运行时出现警告:"CLIENT OF UIKIT REQUIRES UPDATE: This process does not adopt UIScene lifecycle"
npx cap migrate输出UIScene migration: project is in a partial state ... Skipping automated migration- 添加场景清单后,自定义 URL scheme、通用链接、
appUrlOpen或 AppDelegateapplicationDidBecomeActive代码停止运行 - 升级到 8.5 后,
tmpWindow或TmpViewController出现构建错误 - 插件作者询问其 iOS 代码能否适应场景生命周期
- 准备从 8.4 或更早版本升级 Capacitor 8 -> 9
请勿用于:
- Capacitor 8 -> 9 的软件包/原生升级本身 ->
capacitor-app-upgrade-v8-to-v9/capacitor-plugin-upgrade-v8-to-v9 - 从头设置深层链接 ->
capacitor-deep-linking - Android(UIScene 仅适用于 iOS)
- 超出生命周期迁移范围的多窗口或可调整大小布局工作
实时项目快照
!node -e "const fs=require('fs');if(!fs.existsSync('package.json'))process.exit(0);const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));const out=[];for(const s of ['dependencies','devDependencies','peerDependencies']){for(const [n,v] of Object.entries(pkg[s]||{})){if(n.startsWith('@capacitor/'))out.push(s+'.'+n+'='+v)}}const p='ios/App/App/';out.push('SceneDelegate.swift='+fs.existsSync(p+'SceneDelegate.swift'));if(fs.existsSync(p+'Info.plist'))out.push('UIApplicationSceneManifest='+fs.readFileSync(p+'Info.plist','utf8').includes('UIApplicationSceneManifest'));if(fs.existsSync(p+'AppDelegate.swift'))out.push('configurationForConnecting='+fs.readFileSync(p+'AppDelegate.swift','utf8').includes('UISceneConfiguration(name:'));console.log(out.join('\n'))"
审计所依据的事实
- 存在
UIApplicationSceneManifest后,iOS 将不再调用以下 AppDelegate 方法:application(_:open:options:)、application(_:continue:restorationHandler:)、applicationDidBecomeActive、applicationWillResignActive、applicationDidEnterBackground、applicationWillEnterForeground。其中的代码会静默停止运行,不会出现编译器警告。 - AppDelegate 上的以下方法仍会被调用:
didFinishLaunchingWithOptions、applicationWillTerminate、推送令牌注册(didRegisterForRemoteNotificationsWithDeviceToken/didFailToRegister...)以及远程通知的传递。UIApplication.*Notification生命周期通知仍会触发。 SceneDelegateProxy(CAPSceneDelegateProxy.swift)会重新发布.capacitorOpenURL、.capacitorOpenUniversalLink(载荷相同)和CDVPluginHandleOpenURL,并将 URL 同步到ApplicationDelegateProxy.shared.lastURL。冷启动 URL 和用户活动会暂存,直到首次触发.capacitorViewDidAppear,这样插件就能在注册后收到它们,且App.getLaunchUrl()可以正常工作。- 仅在 8.5 中新增的通知:
.capacitorSceneWillConnect、.capacitorSceneOpenURL、.capacitorSceneOpenUniversalLink(object =UIScene,载荷位于userInfo)。8.4 中不会发布这些通知。 - JS
pause/resume由UIScene.didEnterBackgroundNotification/willEnterForegroundNotification驱动,并筛选为桥接对应的场景。 - 8.5 中已移除:
TmpViewController、CapacitorBridge.tmpWindow、tmpViewControllerAppeared。任何引用都会导致构建错误;请从bridge.viewController呈现。 - 8.5 的 SceneDelegate 会通过代码创建窗口和
CAPBridgeViewController()。如果在Main.storyboard中设置了自定义桥接子类,则必须改为在 SceneDelegate 中实例化。 cap migrate(CLI 8.5+)会根据三个信号对项目进行分类:Info.plist中的清单、磁盘上的SceneDelegate.swift、AppDelegate.swift中的UISceneConfiguration(name:。0/3 -> 执行迁移;3/3 -> 跳过,视为已完成;1-2/3 -> 警告“partial state”并不做任何操作。它绝不会覆盖现有的SceneDelegate.swift。对于UIApplication.shared.applicationState、tmpWindow、TmpViewController以及自定义的open url:/continue userActivity:函数体,它会发出警告(但不会修复)。
流程
先审计并报告,然后提问,最后再编辑。用户看到审计结果之前,绝不编辑。
- 检测仓库类型。
ios/App/App.xcodeproj-> 应用。Package.swift/*.podspec中包含CAPPlugin源码,且没有应用项目 -> 插件;转到第 9 步。 - 检查版本和包管理器。
@capacitor/ios必须达到^8.5.0。ios/App/Podfile-> CocoaPods;ios/App/CapApp-SPM-> SPM。两者的 SceneDelegate 相同。 - 根据快照的三个信号进行分类(符合条件 / 已迁移 / 部分迁移)。
- 使用
references/audit-patterns.md中的命令进行审计,检查ios/App和node_modules中已安装的插件。记录每个命中的文件和行号。 - 按以下类别分组报告: 构建阻塞项(
tmpWindow、TmpViewController)、需要决定的事项(自定义 URL / 活动 / 生命周期代码、现有 SceneDelegate、storyboard 自定义类、第三方 SDK 转发)、提示信息(仍然有效的观察者、applicationState、node_modules中的问题)。 - 在需要实际判断的地方提问:
open url:/continue userActivity:中的自定义代码:将其移到scene(_:openURLContexts:)/scene(_:continue:)中,放在代理调用旁边(先展示代码)。- 自定义生命周期代码:移到
sceneDidBecomeActive/sceneWillResignActive/sceneDidEnterBackground/sceneWillEnterForeground,或移到UIApplication通知观察者中。 - 删除现已失效的 AppDelegate URL 方法,或保留(无害)。
- 需要 URL 转发的 SDK(Facebook、Google Sign-In、OAuth 重定向库):查看其文档,确认是否有 SceneDelegate 入口点,并从 SceneDelegate 调用该入口点。
- 应用修改。
- 符合条件且采用模板结构:执行
npm i @capacitor/core@^8.5.0 @capacitor/ios@^8.5.0、npm i -D @capacitor/cli@^8.5.0,然后执行npx cap migrate。逐行阅读所有日志;本步骤中任何被跳过的内容都只需手动处理。然后应用第 6 步中的决定。 - 部分迁移或经过自定义:使用
references/scene-delegate-template.md仅合并缺失部分。保留现有键、方法和自定义逻辑。按照references/pbxproj-registration.md注册新文件。
- 符合条件且采用模板结构:执行
- 同步并构建: 执行
npx cap sync ios,然后构建(参见“验证”)。重新检查三个信号:必须全部为真。 - 插件仓库: 使用
references/plugin-audit.md进行审计并仅提供建议。绝不修改插件仓库中的应用文件;只有用户要求时才编辑插件源码。
验证
npx cap sync ios
xcodebuild -project ios/App/App.xcodeproj -scheme App -configuration Debug \
-destination 'generic/platform=iOS Simulator' build # use -workspace App.xcworkspace for CocoaPods
grep -rn --include=*.swift -E '\btmpWindow\b|\bTmpViewController\b' ios/App # expect nothing
在设备或模拟器上:
- 应用启动后显示 Web 视图(没有黑屏)。
- 后台运行后再回到前台:JS 的
pause和resume各触发一次。 - 自定义 scheme,应用处于运行状态时:触发
appUrlOpen。使用xcrun simctl openurl booted myscheme://test。 - 自定义 scheme,冷启动(先杀掉应用,再打开链接):触发
appUrlOpen,且App.getLaunchUrl()返回该 URL。 - 如果应用配置了关联域名,则可打开 Universal Link(需要真实域名和 AASA 文件)。
- 推送:仍能收到令牌(
registration事件),且轻点通知会打开应用。 - 任何移过位置的自定义逻辑(例如应用回到前台时的分析、SDK URL 处理程序)都能运行。
错误处理
cap migrate提示“partial state” -> 对于手动编辑过的项目,这是预期情况;请手动合并(第 7 步)。未经询问,不要重置文件。- 迁移后出现黑屏 -> SceneDelegate 未创建窗口,或
Info.plist中的UISceneDelegateClassName与类名不匹配。使用$(PRODUCT_MODULE_NAME).SceneDelegate和模板中的窗口设置。 - AppDelegate 中出现
Cannot find 'SceneDelegate' in scope-> 文件未添加到 App 目标;请参阅references/pbxproj-registration.md。 - 出现
Value of type 'CapacitorBridge' has no member 'tmpWindow'-> 删除该引用;通过bridge?.viewController呈现界面。 - 深层链接在热启动时有效,但冷启动时无效 -> URL 逻辑仍在 AppDelegate 中,或应用在 bridge 出现前读取了 URL;请依赖
appUrlOpen+getLaunchUrl()。 - 未使用自定义 bridge 子类(在
capacitorDidLoad中注册的插件缺失)-> 在scene(_:willConnectTo:)中实例化该子类。 - 手动编辑
pbxproj后 Xcode 拒绝打开项目 -> 还原更改,并通过 Xcode 添加文件。
参考资料
仅在涉及相关主题时加载参考资料。
| 文件 | 加载时机 |
|---|---|
references/audit-patterns.md | 第 4 步:grep 命令以及如何对每个匹配项分类 |
references/scene-delegate-template.md | 创建或合并 SceneDelegate、Info.plist 清单、AppDelegate 钩子、自定义子类和生命周期变体时 |
references/pbxproj-registration.md | 不使用 Xcode 注册 SceneDelegate.swift,或验证 CLI 是否已注册时 |
references/plugin-audit.md | 仓库是插件,或已安装的插件出现生命周期匹配项时 |
相关技能:capacitor-app-upgrade-v8-to-v9、capacitor-plugin-upgrade-v8-to-v9、capacitor-app-upgrades、capacitor-deep-linking、capacitor-push-notifications。