iosswiftxcodewatchoswidgetkit

How to use the #Preview macro in a watchOS 10 and iOS 17 Widget Extension that shares the same code (Xcode 15 Beta 4)


Please note, this question is regarding Xcode 15 beta 4, iOS 17 beta 3, and watchOS 10 beta 3

I have a watchOS 10 App with iOS 17 companion app with two Widget (WidgetKit) Extension Targets. Both targets share the same codebase where each Swift file has both targets set in the Target Membership settings.

Some parts of my code are behind #if os(watchOS) compiler directives, for example when setting the supported widget families:

#if os(watchOS)
    .supportedFamilies([.accessoryCircular, .accessoryRectangular])
#else
    .supportedFamilies([.accessoryCircular, .systemSmall, .systemMedium])
#endif

I got multiple Widgets that are made available through a WidgetBundle. However, the following issues also exist when there is only one widget.

This is my current WidgetBundle and preview code:

@main
struct WidgetExtensionBundle: WidgetBundle {
    var body: some Widget {

        StaticSampleWidget()

        if #available(iOSApplicationExtension 17.0, *) {
            AppIntentSampleWidget()
        }
    }
}


#if os(watchOS)
#Preview(as: .accessoryRectangular) {
    StaticSampleWidget()
} timeline: {
    StaticSampleWidget_Entry(date: .now, emoji: "😀")
    StaticSampleWidget_Entry(date: .now, emoji: "🤩")
}
#else
#Preview(as: .systemSmall) {
    StaticSampleWidget()
} timeline: {
    StaticSampleWidget_Entry(date: .now, emoji: "😀")
    StaticSampleWidget_Entry(date: .now, emoji: "🤩")
}
#endif

When I try to use the preview in Xcode, it cannot build the app and show no previews. By trying different settings and combinations of selected targets, I sometimes get at least the iOS preview running, but never the watchOS preview. Also, the first #Preview behind the #if os(watchOS) is grayed out. On the Canvas it always shows two Preview tabs, even though it should only be one depending on the selected target. I sometimes get the following errors, and sometimes it doesn't tell me at all what is wrong:

Sample.watchkitapp.WidgetExtensionWatch.appex with id xxx.xxx.Watch- Sample.watchkitapp.WidgetExtensionWatch does not define either an NSExtensionMainStoryboard or NSExtensionPrincipalClass key with a string value

Failed to build WidgetExtensionBundle.swift

How do I correctly setup my targets, and preview code to be able to show the relevant previews for iOS and watchOS?


Solution

  • After several trials and errors, I figured out the issues I had. I'll explain step by step what I've done to get it running in my case. Please note, there might be certain settings that could still prevent the preview from working, but this worked for me. Also, the iOS/watchOS and Xcode versions are still in Beta. Things might change and not work the same after a future release.

    First I confirmed the following settings:

    Most importantly I changed the preview code to the following:

    #if os(watchOS)
    let previewWidgetFamily: WidgetFamily = .accessoryRectangular
    #else
    let previewWidgetFamily: WidgetFamily = .systemSmall
    #endif
    
    #Preview(as: previewWidgetFamily) {
        StaticSampleWidget()
    } timeline: {
        StaticSampleWidget_Entry(date: .now, emoji: "😀")
        StaticSampleWidget_Entry(date: .now, emoji: "🤩")
    }
    

    I think this might be a bug with the new Swift macros. But by putting macros and the compiler directive separate, nothing was grayed out anymore and only one preview tab was shown.

    And finally, the preview was working with the correct device.