如何在共享代码的watchOS10/iOS17 Widget扩展中使用#Preview宏(Xcode15 Beta4)
解决Xcode 15 Beta中iOS/watchOS共享Widget代码的预览问题
问题根源
- 同一个
@main标记的WidgetBundle同时属于iOS和watchOS两个扩展目标,导致watchOS扩展的Info.plist无法正确识别主类,触发NSExtensionPrincipalClass缺失错误。 - 共享文件中的条件编译预览代码被Xcode同时解析为两个平台的预览,导致画布显示多余标签,且watchOS预览因主类关联问题无法加载。
解决步骤
1. 拆分平台专属的WidgetBundle主类
将原有的单一WidgetBundle拆分为iOS和watchOS各自独立的@main结构,通过条件编译区分:
#if os(watchOS) @main struct WatchWidgetBundle: WidgetBundle { var body: some Widget { StaticSampleWidget() // 若watchOS需要AppIntent Widget,可在此添加(需适配watchOS版本) } } #else @main struct iOSWidgetBundle: WidgetBundle { var body: some Widget { StaticSampleWidget() if #available(iOSApplicationExtension 17.0, *) { AppIntentSampleWidget() } } } #endif
删除原有的WidgetExtensionBundle结构,确保每个平台的扩展目标拥有自己的主入口类。
2. 修正扩展的Info.plist配置
针对两个Widget扩展目标分别配置Info.plist:
- watchOS Widget扩展:
找到NSExtension->NSExtensionAttributes,添加或修改NSExtensionPrincipalClass字段,值为你的模块名.WatchWidgetBundle(例如模块名为WidgetExtensionWatch,则填WidgetExtensionWatch.WatchWidgetBundle)。 - iOS Widget扩展:
同理,将NSExtensionPrincipalClass设置为你的模块名.iOSWidgetBundle(例如WidgetExtension.iOSWidgetBundle)。
3. 调整预览代码的条件编译逻辑
保留原有条件编译,为预览添加明确名称帮助Xcode区分平台预览:
#if os(watchOS) #Preview("Watch 矩形组件", as: .accessoryRectangular) { StaticSampleWidget() } timeline: { StaticSampleWidget_Entry(date: .now, emoji: "😀") StaticSampleWidget_Entry(date: .now, emoji: "🤩") } #else #Preview("iOS 小型组件", as: .systemSmall) { StaticSampleWidget() } timeline: { StaticSampleWidget_Entry(date: .now, emoji: "😀") StaticSampleWidget_Entry(date: .now, emoji: "🤩") } #endif
切换预览目标时,Xcode会自动加载对应平台的预览代码,不再显示多余标签。
4. 清理项目并重建
- 执行
Product > Clean Build Folder清理构建缓存。 - 关闭Xcode,删除DerivedData文件夹(路径:
~/Library/Developer/Xcode/DerivedData)。 - 重新打开项目,选择对应平台的Widget扩展目标,点击预览按钮加载画布。
验证效果
- 选择iOS Widget扩展目标时,画布仅显示iOS平台的预览,可正常加载。
- 选择watchOS Widget扩展目标时,原灰色的watchOS预览会正常激活并显示画布。
内容的提问来源于stack exchange,提问作者Marco Boerner
相关产品推荐
相关产品推荐

