iosswiftwidgetwidgetkitios17

Use disfavoredLocations(_:for:) for Widget below iOS 17


I have multiple Widgets for my iOS/iPadOS/watchOS app. I have one widget with supported family .systemLarge.

struct MyWidget: Widget {

    var body: some WidgetConfiguration {
        StaticConfiguration(...)
            .xxx // some modifiers
            .supportedFamilies([.systemLarge])
    }

}

I want this widget to be supported on new stand-by location so I need to support family .systemSmall as well (I want to reuse body). But since I don't want this .systemSmall widget in home screen's gallery I need to use .disfavoredLocations([.homeScreen], for: [.systemSmall]) as well.

Unfortunetely, I'm supporting iOS 15 and 16 as well so I need to wrap this modifier somehow into #available(iOS 17.0, *). But... this can't be done since there is no type-erased WidgetConfiguration.

Then I was thinking about something like this with the help of WidgetBundle:

@available(iOS 17.0, *)
struct MyWidgetIOS17: Widget {

    var body: some WidgetConfiguration {
        var widget = MyWidget()
        return widget.body.disfavoredLocations([.homeScreen], for: [.systemSmall])
    }

}

@main
struct Widgets: WidgetBundle {
    var body: some Widget {
        if #available(iOS 17.0, *) {
            MyWidgetIOS17()
        } else {
            MyWidget()
        }
    }
}

But... while

if #available(iOS 17.0, *) {
    MyWidgetIOS17()
}

works,

if #available(iOS 17.0, *) {
    MyWidgetIOS17()
} else {
    MyWidget()
}

does not.

Does anybody know how to use disfavoredLocations below iOS 17 while still reusing the Widgets body?


Solution

  • I solved this issue with an extension on WidgetConfiguration which uses disfavoredLocations just for iOS 17+

    enum WidgetDisfavoredLocation {
        case homeScreen
        case lockScreen
        case standBy
        case iPhoneWidgetsOnMac
    }
    
    extension WidgetConfiguration {
        func backDeployedDisfavoredLocations(
            _ locations: [WidgetDisfavoredLocation],
            for families: [WidgetFamily]
        ) -> some WidgetConfiguration {
            if #available(iOS 17.0, *) {
                return disfavoredLocations(
                    locations.map { location in
                        switch location {
                        case .homeScreen:
                            return .homeScreen
                        case .lockScreen:
                            return .lockScreen
                        case .standBy:
                            return .standBy
                        case .iPhoneWidgetsOnMac:
                            return .iPhoneWidgetsOnMac
                        }
                    },
                    for: families
                )
            } else {
                return self
            }
        }
    }