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/

Thanks for bringing this to our attention and this issue should be fixed in Xcode 16.1 beta 3 with the updated SDKs. Please file another feedback if that's not the case, and reply here with the feedback number. Thank you very much!

I had to use a mix of things:

import WidgetKit
import SwiftUI


@main
struct widgetBundle: WidgetBundle {
    
  @WidgetBundleBuilder
    private var iOSAllWidgets: some Widget {
        LockScreenWidget()
    }
    
    @available(iOSApplicationExtension 17.0, iOS 17.0, *)
    @WidgetBundleBuilder
    private var iOS17Widgets: some Widget {
        LockScreenWidget()
        LiveActivityWidget()
    }
    
    @available(iOSApplicationExtension 18.0, iOS 18.0, *)
    @WidgetBundleBuilder
    private var iOS18Widgets: some Widget {
        LockScreenWidget()
        LiveActivityWidget()
        LauncherControlWidget()
    }
    
    var body: some Widget {
        if #available(iOSApplicationExtension 18.0, *) {
            return iOS18Widgets
        } else {
            return getWidgetsThatAreNOTControlWidgets()
        }
    }
    
 private func getWidgetsThatAreNOTControlWidgets() -> some Widget {
        if #available(iOSApplicationExtension 17.0, *) {
            return iOS17Widgets
        } else {
            return iOSAllWidgets
        }
    }
    
}

If you add more than one if else block in the body it complains about control flow statements. You can't use WidgetBundleBuilder.buildBlock for control widgets. If you try to use the function alone, it complains that the function does not return the same type for all the return statements because somehow control widget doesn't conform to widget.

This will have to do until we get the fix in Xcode 16.1 beta 3.

WidgetBundle with ControlWidget does not work on iOS 17
 
 
Q