Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Variable WidgetBundle configuration based on conditions

The WidgetBundle protocol inside WidgetKit allows you to configure supported widgets for your apps. Whenever you add one or more widgets, you’ll have to add a @main struct conforming to this protocol. Like SwiftUI views, you’ll return the supported widgets inside the body computed property.

You’ll not have issues with a consistent set of supported widgets. However, errors appear as soon as you want to return a set of widgets conditionally. Let’s dive into a workaround for this problem and help you support several widgets based on conditions.

Creating a WidgetBundle

Let’s start by looking at a standard implementation of a consistent set of widgets. For my Stock Analyzer app, this would look as follows:

@main
struct StockAnalyzerWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        StockAnalyzerWatchlistWidgets()
        StockAnalyzerSymbolWidgets()
    }
}

The WidgetBundle protocol requires us to return an opaque Widget type through the computed body property. If you’re new to the some keyword, I encourage you to read my article Some keyword in Swift: Opaque types explained with code examples. In short, we’ll have to return a consistent type for the method’s scope.

The body method comes with the @WidgetBundleBuilder attribute, which is comparable to the @ViewBuilder attribute in SwiftUI. It allows us to return a consistent set of widgets without using a return keyword or array element.

Stay updated with the latest in SwiftUI

Join 20,010 Swift developers in our exclusive newsletter for the latest insights, tips, and updates. Don't miss out – join today!

You can always unsubscribe, no hard feelings.

Using conditions inside a WidgetBundle

The earlier shared example of a consistent set of widgets will work out great if your group of widgets doesn’t change inside the WidgetBundle. However, in the case of Stock Analyzer, I only wanted to add my StockAnalyzerSymbolWidgets for iOS 16 and up. As soon as I added a control flow statement, I would run into the following error:

WidgetBundle result builder does not support control flow statements.
WidgetBundle result builder does not support control flow statements.

Xcode will tell you:

Closure containing control flow statement cannot be used with result builder ‘WidgetBundleBuilder’

As stated, WidgetBundleBuilder is an example of a result builder. Read my article Result builders in Swift explained with code examples if you want to learn more about the implementation details. In this case, WidgetBundleBuilder lacks support for control flow statements since it doesn’t implement the buildEither result builder methods.

To work around this problem, we’ll have to create a separate method returning an opaque Widget type without the WidgetBundleBuilder attribute. We will be able to use a control flow statement inside this method since we no longer have the restriction of the result builder:

@main
struct StockAnalyzerWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        widgets()
    }

    func widgets() -> some Widget {
        if #available(iOS 16.0, *) {
            return WidgetBundleBuilder.buildBlock(StockAnalyzerWatchlistWidgets(), StockAnalyzerSymbolWidgets())
        } else {
            return StockAnalyzerWatchlistWidgets()
        }
    }
}

We can make use of the WidgetBundleBuilder static methods directly to get back an opaque Widget return type in all cases. Based on the iOS 16 conditional if statement, we’re either returning both our widgets or just the single watchlist widget.

The body method will accept using the new widgets() method since it’s returning a consistent opaque type for the method’s scope. Altogether, we’ve enabled ourselves to produce a different set of widgets conditionally inside our WidgetBundle.

Conclusion

WidgetBundle works excellently if you have a consistent set of widgets. Whenever you want to return a different set of widgets based on a condition, you’ll have to make use of a separate method returning an opaque type Widget.

If you want to improve your SwiftUI knowledge, even more, check out the SwiftUI category page. Feel free to contact me or tweet me on Twitter if you have any additional tips or feedback.

Thanks!

 
Antoine van der Lee

Written by

Antoine van der Lee

iOS Developer since 2010, former Staff iOS Engineer at WeTransfer and currently full-time Indie Developer & Founder at SwiftLee. Writing a new blog post every week related to Swift, iOS and Xcode. Regular speaker and workshop host.

Are you ready to

Turn your side projects into independence?

Learn my proven steps to transform your passion into profit.