WidgetKit works in Simulator but not on device

I can't get the simplest Widget to work. I simply want to present the app's icon. It does what it's supposed to do in the simulator but on the actual device it only says "Please adopt containerBackground API" at the place where the Widget would have shown otherwise.

I found out that I am supposed to add .containerBackground(Color.clear, for: .widget) for no reason at all to make this work. However, when I do that, I just get a gray circle where the app icon was supposed to show. The png files should be fine because again on the simulator it works so I have no idea what I am doing wrong.

If someone can tell me what I'm doing wrong or if someone can share their Widget class that would be awesome - I literally only want to show my app's icon on the Watch face but I already fail at that, no idea why they made WidgetKit impossible to work with.

import SwiftUI
import WidgetKit

struct IconEntry: TimelineEntry {
    let date = Date()
    let icon: Image
}

struct IconProvider: TimelineProvider {
    
    func placeholder(in context: Context) -> IconEntry {
        return IconEntry(icon: getImageForContext(context, color: false))
    }
    
    func getSnapshot(in context: Context, completion: @escaping (IconEntry) -> ()) {
        let entry = IconEntry(icon: getImageForContext(context, color: false))
        completion(entry)
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let entry = IconEntry(icon: getImageForContext(context, color: false))
        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }
}

struct IconWidget: Widget {
    
    let kind: String = "IconWidget"
    
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: IconProvider()) { entry in
            IconView()
                .containerBackground(Color.clear, for: .widget)
        }
        .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular])
        .description(Text("Show the app's icon on a watch face."))
    }
}

struct InlineWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "Inline", provider: IconProvider()) { entry in
            Text("App Title")
                .containerBackground(Color.clear, for: .widget)
        }
        .configurationDisplayName("App Title")
        .description("Description")
        .supportedFamilies([.accessoryInline])
    }
}


func getImageForContext(_ context: TimelineProviderContext, color: Bool) -> Image {
    
    var fileNameToUse = "iconColor.png"
    
    if(!color) {
        fileNameToUse = "iconAlpha.png"
    }
    
    let displaySize = context.displaySize
    
    if(displaySize.width == 0 || displaySize.height == 0) {
        return Image(uiImage: .init())
    }
    
    let uiImage = UIImage(imageLiteralResourceName: fileNameToUse).resizeImage(width: displaySize.width, height: displaySize.height)
    let image = Image(uiImage: uiImage)
    return image
}

struct IconView: View {
    
    @Environment(\.widgetRenderingMode) var widgetRenderingMode
    
    var body: some View {
        imageFor(widgetRenderingMode)
            .resizable()
            .onAppear {
                print("ICON VIEW \(widgetRenderingMode == .fullColor ? "COLOR" : "ALPHA")")
            }
            .containerBackground(Color.clear, for: .widget)
    }
}

private func imageFor(_ widgetRenderingMode: WidgetRenderingMode) -> Image {
    switch widgetRenderingMode {
    case .fullColor:
        return Image(uiImage: UIImage(imageLiteralResourceName: "iconColor.png"))
    default:
        return Image(uiImage: UIImage(imageLiteralResourceName: "iconAlpha.png"))
    }
}


@main
struct IconBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        IconWidget()
        InlineWidget()
    }
}

extension UIImage {
    func resizeImage(width: CGFloat, height: CGFloat) -> UIImage {
        let edgeLength = min(width, height)
        let newSize = CGSize(width: edgeLength, height: edgeLength)
        UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
        self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
        if let resizedImage = UIGraphicsGetImageFromCurrentImageContext() {
            UIGraphicsEndImageContext()
            return resizedImage
        } else {
            return UIImage()
        }
    }
}

Replies

I probably found a solution. Surprise surprise it's another thing not at all documented by Apple. Will update you tomorrow

Now this should work. "color" is the full color PNG, "alpha" is a transparent PNG. It's blurry on the X-Large watch face but because the watch face requests the wrong resolution. You can do a 2x on the scale however that will break the other complications.

Maybe there is a way to know that the Watch requests the icon for the X-Large watch face but I stopped caring, it's stupid to spend so much time on something that simple, not gonna touch this again

import SwiftUI
import WidgetKit

@main
struct IconBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        IconWidget()
        InlineWidget()
    }
}

struct IconProvider: TimelineProvider {
    
    func placeholder(in context: Context) -> IconEntry {
        return IconEntry(context: context)
    }
    
    func getSnapshot(in context: Context, completion: @escaping (IconEntry) -> ()) {
        let entry = IconEntry(context: context)
        completion(entry)
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let entry = IconEntry(context: context)
        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }
}

struct IconWidget: Widget {
    let kind: String = "IconWidget"
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: IconProvider()) { entry in
            IconView(entry: entry)
                .containerBackground(Color.clear, for: .widget)
        }
        .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular])
        .description(Text("Description"))
    }
}

struct InlineWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "Inline", provider: IconProvider()) { entry in
            Text("App Title")
        }
        .configurationDisplayName("App Title")
        .description("Description")
        .supportedFamilies([.accessoryInline])
    }
}

struct IconView: View {
    @Environment(\.widgetRenderingMode) var widgetRenderingMode
    let entry: IconEntry
    var body: some View {
        imageFor(widgetRenderingMode, entry: entry)
            .resizable()
    }
}

private func imageFor(_ widgetRenderingMode: WidgetRenderingMode, entry: IconEntry) -> Image {
    var image: UIImage
    let size = entry.context.displaySize

    switch widgetRenderingMode {
    case .fullColor:
        image = UIImage(imageLiteralResourceName: "color.png")
    default:
        image = UIImage(imageLiteralResourceName: "alpha.png")
    }
    return Image(uiImage: image.resizeImage(width: size.width, height: size.height))
}

struct IconEntry: TimelineEntry {
    let date = Date()
    let context: TimelineProviderContext
}

extension UIImage {
    func resizeImage(width: CGFloat, height: CGFloat) -> UIImage {
        let edgeLength = min(width, height)
        let newSize = CGSize(width: edgeLength, height: edgeLength)
        UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
        self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
        if let resizedImage = UIGraphicsGetImageFromCurrentImageContext() {
            UIGraphicsEndImageContext()
            return resizedImage
        } else {
            return UIImage()
        }
    }
}