How to use Available Macro With SwiftUI modifiers?

It’s unclear how to use if #available(macOS 15.0, *) { without duplicating a scene for ClientApp: App

var body: some Scene {
        Window("Obscura", id: WindowIds.RootWindowId) {
            Group {} 
        }
        // https://developer.apple.com/documentation/swiftui/scene/defaultlaunchbehavior(_:)
                .defaultLaunchBehavior(.suppressed)
    }

How do I use defaultLaunchBehavior condtionally when it is available (macOS 15+)?

I found Availability for modifiers - #4 by tera and Simplifying Backwards Compatibility in Swift | Dave DeLong , but these are from 2021. Is there a better way to do it in 2025? I’m in the process of implementing the same thing but it seems I can’t just backport a modifier like defaultLaunchBehavior but rather I have to create helper functions like launchSuppressed because the type SceneLaunchBehavior itself is restricted to macOS 15.

Also, I need to use SceneBuilder not ViewBuilder, so now I am getting errors like

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

Not a full answer to your question, but per https://developer.apple.com/documentation/swiftui/scenebuilder/buildoptional(_:):

Conditional statements in a SceneBuilder can contain an if statement but not an else statement, and the condition can only perform a compiler check for availability, like in the following code:

var body: some Scene {
    if #available(iOS 16, *) {
        WindowGroup {
            ContentView()
        }
    }
}

So you would have to put the two branches in separate if #available and if #unavailable blocks.

Getting a

”Failed to produce diagnostic for expression; please submit a bug report (https://swift.org/contributing/#reporting-bugs)”

This works:

    var body: some Scene {
        if #available(macOS 15.0, *) {
            return Window("Obscura", id: "1") {
                Group {} 
            }
            .defaultLaunchBehavior(.suppressed)
        } else {
            return Window("Obscura", id: "1") {
                Group {} 
            }
        }
    }

I am not entirely sure why "return" is needed here compared to normal Swift control flow where you won't need it.

If you want a DRY-er version this also works:

    var body: some Scene {
        let window = Window("Obscura", id: "1") {
            Group {} 
        }
        if #available(macOS 15.0, *) {
            return window.defaultLaunchBehavior(.suppressed)
        } else {
            return window
        }
    }
2 Likes

If you’re able to reproduce that in a sample project, please submit a bug report as it suggests! Hopefully @tera’s workaround is able to help.

Apple provides very limited support for conditionals in SceneBuilder. In your case, it might be easier to hoist the conditional out of the body. Start by moving the “Obscura” scene to its own type:

struct ObscuraScene: Scene {
  var body: some Scene {
    Window("Obscura", id: "obscura") {
      // ...
    }
  }
}

Then define an App-conforming type that applies the defaultLaunchBehavior modifier:

@available(macOS 15, *)
struct App15: App {
  var body: some Scene {
    ObscuraScene()
      .defaultLaunchBehavior(.suppressed)
  }
}

and another App-conforming type that doesn't apply the modifier:

struct AppLegacy: App {
  var body: some Scene {
    ObscuraScene()
  }
}

Note that you do not apply the @main attribute to either of those App types. Instead, write your own main function that calls the main of the appropriate App type:

@main
enum Main {
  static func main() {
    if #available(macOS 15, *) {
      App15.main()
    } else {
      AppLegacy.main()
    }
  }
}

When applying the mentioned "backport" idea:

extension Backport where Content: Scene {
    @SceneBuilder func defaultLaunchBehaviorSuppressed() -> some Scene {
        if #available(macOS 15, *) {
            return content.defaultLaunchBehavior(.suppressed) // ⚠️
        } else {
            return content
        }
    }
}

I am getting this warning:

⚠️ Application of result builder 'SceneBuilder' disabled by explicit 'return' statement

When seeing such a warning that "result builder is disabled" should I be concerned or is it benign?


Relates to Rob's:

interestingly ViewBuilder doesn't require returns:

extension Backport where Content: View {
    @ViewBuilder func test() -> some View {
        if #available(iOS 15, *) {
            // return is not needed here
            content.focusable() // for test
        } else {
            content 
        }
    }
}

What would maintainers of SceneBuilder have to modify should they want SceneBuilder working the same way?

Just remove the application of @SceneBuilder altogether. As the warning is telling you, the result builder has no effect, and your code isn’t even calling SceneBuilder.buildLimitedAvailability(:_) here but instead you end up getting the effect of SE-0360 Opaque result types with limited availability, which is much better than that!

I've explained it more in this post: Availability for modifiers - #19 by pyrtsa

1 Like

Hey guys really thankful for the education. Seems like I solved my original bug by relaunching the app when no window exists? The original bug being that when our app is launched at login, our intended behaviour was that there is no window shown, however our status menu “open window” feature wouldn’t work on macOS 15+, which is why I was exploring ways to fix the issue. One way was to conditionally compile with default launch behaviour, however a simpler solution ended up being using our existing URI protocol to relaunch the app when there are no windows to bring to front which worked as desired.

I’m not sure if I should create a new thread for what the best practice is to implement such a feature. Again, thank you all for the learning. We’re really trying to build our apps to prevent future macOS versions from causing our code to become buggy, so I’m also exploring simply using NSWindow and then hosting a SwiftUI Group inside that.