WidgetBundle with ControlWidget does not work on iOS 17

I'm trying to add a ControlWidget to my WidgetBundle like this:

struct MyWidgets: WidgetBundle {

  var body: some Widget {
    if #available(iOSApplicationExtension 18.0, *) {
      LauncherControl()
    }

    MyLiveActivityWidget()
    HomeScreenWidget()
    LockScreenWidget()
  }

This works exactly as expected on iOS 18. However on iOS 17 my app seems to have no widgets at all.

The workaround described here (https://www.avanderlee.com/swiftui/variable-widgetbundle-configuration/) does not work either since WidgetBundleBuilder.buildBlock does not accept ControlWidget as an argument.

What is the correct way to include a Control widget conditionally on iOS 18?

Answered by ekurutepe in 795557022

The following seems to work on both iOS 17 and iOS 18

  var body: some Widget {
    if #available(iOSApplicationExtension 18.0, *) {
      return iOS18Widgets
    } else {
      return iOS17Widgets
    }
  }

  @WidgetBundleBuilder
  var iOS17Widgets: some Widget {
    LiveActivityWidget()
    HomeScreenWidget()
    LockScreenWidget()
  }

  @available(iOSApplicationExtension 18.0, *)
  @WidgetBundleBuilder
  var iOS18Widgets: some Widget {
    LauncherControl()
    LiveActivityWidget()
    HomeScreenWidget()
    LockScreenWidget()
  }
Accepted Answer

The following seems to work on both iOS 17 and iOS 18

  var body: some Widget {
    if #available(iOSApplicationExtension 18.0, *) {
      return iOS18Widgets
    } else {
      return iOS17Widgets
    }
  }

  @WidgetBundleBuilder
  var iOS17Widgets: some Widget {
    LiveActivityWidget()
    HomeScreenWidget()
    LockScreenWidget()
  }

  @available(iOSApplicationExtension 18.0, *)
  @WidgetBundleBuilder
  var iOS18Widgets: some Widget {
    LauncherControl()
    LiveActivityWidget()
    HomeScreenWidget()
    LockScreenWidget()
  }

@ekurutepe, thanks for sharing your insights! With your proposed solution, I get the error:

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

How are you able to circumvent this limitation?

@devdevdev I think this should work

var body: some Widget {
    widgets()
}

func widgets() -> some Widget {
    if #available(iOSApplicationExtension 18.0, *) {
        return iOS18Widgets
    } else {
        return iOS17Widgets
    }
}
  

@WidgetBundleBuilder
var iOS17Widgets: some Widget {
    LiveActivityWidget()
    HomeScreenWidget()
    LockScreenWidget()
}

@available(iOSApplicationExtension 18.0, *)
@WidgetBundleBuilder
var iOS18Widgets: some Widget {
    LauncherControl()
    LiveActivityWidget()
    HomeScreenWidget()
    LockScreenWidget()
}

It combines the idea of this thread with the workaround mentioned in https://www.avanderlee.com/swiftui/variable-widgetbundle-configuration/

WidgetBundle with ControlWidget does not work on iOS 17
 
 
Q