Supporting WidgetKit Size Families in SwiftUI

In the chapter titled Building Widgets with SwiftUI and WidgetKit, we learned that a widget is able to appear in small, medium and large sizes. The project created in the previous chapter included a widget view designed to fit within the small size format. Since the widget did not specify the supported sizes, it would still be possible to select a large or medium sized widget from the gallery and place it on the home screen. In those larger formats, however, the widget content would have filled only a fraction of the available widget space. If larger widget sizes are to be supported, the widget should be designed to make full use of the available space.

In this chapter, the WidgetDemo project created in the previous chapter will be modified to add support for the medium widget size.

Supporting Multiple Size Families

Begin by launching Xcode and loading the WidgetDemo project from the previous chapter. As outlined above, this phase of the project will add support for the medium widget size (though these steps apply equally to adding support for the large widget size).

In the absence of specific size configuration widgets are, by default, configured to support all three size families. To restrict a widget to specific sizes, the supportedFamilies() modifier must be applied to the widget configuration.

To restrict the widget to only small and medium sizes for the WidgetDemo project, edit the WeatherWidget.swift file and modify the WeatherWidget declaration to add the modifier. Also take this opportunity to modify the widget display name and description:

@main
struct WeatherWidget: Widget {
    private let kind: String = "WeatherWidget"
 
    public var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider(), placeholder: PlaceholderView()) { entry in
            WeatherWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Weather Widget")
        .description("A demo weather widget.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

To preview the widget in medium format, edit the preview provider to add an additional preview, embedding both in a Group:

struct WeatherWidget_Previews: PreviewProvider {
    static var previews: some View {
       
        Group {
            WeatherWidgetEntryView(entry: WeatherEntry(date: Date(), 
                  city: "London", temperature: 89, 
                  description: "Thunder Storm", icon: "cloud.bolt.rain", 
                        image: "thunder"))
                .previewContext(WidgetPreviewContext(
                                         family: .systemSmall))
        
            WeatherWidgetEntryView(entry: WeatherEntry(date: Date(), 
                  city: "London", temperature: 89, 
                  description: "Thunder Storm", icon: "cloud.bolt.rain", 
                        image: "thunder"))
                .previewContext(WidgetPreviewContext(
                                         family: .systemMedium))
        }
    }
}

When the preview canvas updates, it will now include the widget rendered in medium size as shown in Figure 49-1:

Figure 49-1

Clearly the widget is not taking advantage of the additional space offered by the medium size. To address this shortcoming, some changes to the widget view need to be made.

Adding Size Support to the Widget View

The changes made to the widget configuration mean that the widget can be displayed in either small or medium size. To make the widget adaptive, the widget view needs to identify the size in which it is currently being displayed. This can be achieved by accessing the widgetFamily property of the SwiftUI environment. Remaining in the WeatherWidget.swift file, locate and edit the WeatherWidgetEntryView declaration to obtain the widget family setting from the environment:

struct WeatherWidgetEntryView: View {
    var entry: Provider.Entry
    
    @Environment(\.widgetFamily) var widgetFamily
.
.

Next, embed the subview in a horizontal stack and conditionally display the image for the entry if the size is medium:

struct WeatherWidgetEntryView : View {
    var entry: Provider.Entry
 
    @Environment(\.widgetFamily) var widgetFamily
    
    var body: some View {
 
        ZStack {
            Color("weatherBackgroundColor")
   
            HStack {
                WeatherSubView(entry: entry)
                if widgetFamily == .systemMedium {
                    Image(entry.image)
                        .resizable()
                }
            }
        }
    }
}

When previewed, the medium sized version of the widget should appear as shown in Figure 49-2:

Figure 49-2

To test the widget on a device or simulator, run the extension as before and, once the widget is installed and running, perform a long press on the home screen background. After a few seconds have elapsed, the screen will change as shown in Figure 49-3:

Figure 49-3

Click on the ‘+’ button indicated by the arrow in the above figure to display the widget gallery and scroll down the list of widgets until the WidgetDemo entry appears:

Figure 49-4

Select the WidgetDemo entry to display the widget size options. Swipe to the left to display the medium widget size as shown in Figure 49-5 before tapping on the Add Widget button:

Figure 49-5

On returning to the home screen, click on the Done button located in the top right-hand corner of the home screen to commit the change. The widget will appear as illustrated in Figure 49-6 and update as the timeline progresses:

Figure 49-6

Summary

WidgetKit supports small, medium and large widget size families and, by default, a widget is assumed to support all three formats. In the event that a widget only supports specific sizes, WidgetKit needs to be notified using a widget configuration modifier.

To fully support a size format, a widget should take steps to detect the current size and provide a widget entry layout which makes use of the available space allocated to the widget on the device screen. This involves accessing the SwiftUI environment widgetFamily property and using it as the basis for conditional layout declarations within the widget view.

Now that widget size family support has been added to the project, the next chapter will add some interactive support to the widget in the form of deep linking into the companion app and widget configuration.