mcpField
C
capacitor-uiscene-migration

capacitor-uiscene-migration

github.com/cap-go开发7354 次安装2 周前更新
代码仓库

关于 capacitor-uiscene-migration

用于审计并将 Capacitor iOS 项目从 AppDelegate 生命周期迁移到 UIScene,以满足 Xcode 27 要求。涵盖迁移状态、自定义生命周期与 URL 处理,以及构建和运行检查。

主要功能

  1. 01判定 UIScene 迁移状态
  2. 02审查生命周期与 URL 处理代码
  3. 03指导 AppDelegate 和 SceneDelegate 修改
  4. 04检查插件与 SDK 的生命周期问题
  5. 05验证构建和应用行为

适用场景

  1. 01让 Capacitor 8.4 或更早版本适配 Xcode 27
  2. 02处理 cap migrate 的部分迁移结果
  3. 03采用场景生命周期后保留深层链接
  4. 04检查插件的 UIScene 兼容性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cap-go/capgo-skills --skill capacitor-uiscene-migration

安装后在 Claude Code 中输入/capacitor-uiscene-migration确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.json
references/
audit-patterns.md
pbxproj-registration.md
plugin-audit.md
scene-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/ios 8.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 或 AppDelegate applicationDidBecomeActive 代码停止运行
  • 升级到 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: 函数体,它会发出警告(但不会修复)。

流程

先审计并报告,然后提问,最后再编辑。用户看到审计结果之前,绝不编辑。

  1. 检测仓库类型。 ios/App/App.xcodeproj -> 应用。Package.swift / *.podspec 中包含 CAPPlugin 源码,且没有应用项目 -> 插件;转到第 9 步。
  2. 检查版本和包管理器。 @capacitor/ios 必须达到 ^8.5.0。ios/App/Podfile -> CocoaPods;ios/App/CapApp-SPM -> SPM。两者的 SceneDelegate 相同。
  3. 根据快照的三个信号进行分类(符合条件 / 已迁移 / 部分迁移)。
  4. 使用 references/audit-patterns.md 中的命令进行审计,检查 ios/App 和 node_modules 中已安装的插件。记录每个命中的文件和行号。
  5. 按以下类别分组报告: 构建阻塞项(tmpWindow、TmpViewController)、需要决定的事项(自定义 URL / 活动 / 生命周期代码、现有 SceneDelegate、storyboard 自定义类、第三方 SDK 转发)、提示信息(仍然有效的观察者、applicationState、node_modules 中的问题)。
  6. 在需要实际判断的地方提问:
    • open url: / continue userActivity: 中的自定义代码:将其移到 scene(_:openURLContexts:) / scene(_:continue:) 中,放在代理调用旁边(先展示代码)。
    • 自定义生命周期代码:移到 sceneDidBecomeActive / sceneWillResignActive / sceneDidEnterBackground / sceneWillEnterForeground,或移到 UIApplication 通知观察者中。
    • 删除现已失效的 AppDelegate URL 方法,或保留(无害)。
    • 需要 URL 转发的 SDK(Facebook、Google Sign-In、OAuth 重定向库):查看其文档,确认是否有 SceneDelegate 入口点,并从 SceneDelegate 调用该入口点。
  7. 应用修改。
    • 符合条件且采用模板结构:执行 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 注册新文件。
  8. 同步并构建: 执行 npx cap sync ios,然后构建(参见“验证”)。重新检查三个信号:必须全部为真。
  9. 插件仓库: 使用 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。

评论

支持 Markdown