Home > OS >  How to include a scene in SceneBuilder only for some versions of macOS?
How to include a scene in SceneBuilder only for some versions of macOS?

Time:01-28

My problem is the following: I have an application that uses the MenuBarExtra() scene type, which was introduced in macOS 13. But I want to have a version of the app for macOS 12, too, just without that scene.

I've looked into all sorts of permuations using @available and if #available but haven't been able to come up with something that compiles.

In principle, this is what I would like:

@main
struct MyApp: App {
    @ObservedObject public var userSettings: UserSettings
    @ObservedObject public var viewModel: ViewModel
    /* more variables and initializer declarations */  

   var body: some Scene {
        WindowGroup("Pipelines") {
            PipelineListView(model: viewModel, settings: userSettings)
        }
        .commands {
            AppCommands()
        }
        Settings {
            SettingsView(settings: userSettings)
        }
        if #available(macOS 13.0, *) {
            MenuBarExtra() {
                MenuBarExtraContent(model: viewModel)
            } label: {
                MenuBarExtraLabel(model: viewModel)
            }
        }
    }
}

This code doesn't compile, though. The compiler complains that "Closure containing control flow statement cannot be used with result builder 'SceneBuilder'".

Any ideas what I could try?

CodePudding user response:

You should be able to move the MenuBarExtra code into a separate function, e.g.

func menBarExtra() -> some Scene {
    if #available(macOS 13.0, *) {
        return MenuBarExtra() {
            MenuBarExtraContent(model: viewModel)
        } label: {
            MenuBarExtraLabel(model: viewModel)
        }
    }
}  

and then

@main
struct MyApp: App {
    @ObservedObject public var userSettings: UserSettings
    @ObservedObject public var viewModel: ViewModel
    /* more variables and initializer declarations */  

   var body: some Scene {
        WindowGroup("Pipelines") {
            PipelineListView(model: viewModel, settings: userSettings)
        }
        .commands {
            AppCommands()
        }
        Settings {
            SettingsView(settings: userSettings)
        }
        menBarExtra()
    }
}

CodePudding user response:

SceneBuilder doesn't allow conditionals, and there's no AnyScene type eraser, so you can't have a single MyApp type that returns a different Scene definition depending on the macOS version.

Instead, create two types conforming to App: one for macOS 13 and later, and one for older systems. Do not attach the @main annotation to either type. Factor out the common stuff to a third type that conforms to Scene.

Here's the common Scene:

struct CommonScene: Scene {
    @ObservedObject var userSettings: UserSettings
    @ObservedObject var viewModel: ViewModel

   var body: some Scene {
        WindowGroup("Pipelines") {
            PipelineListView(model: viewModel, settings: userSettings)
        }
        .commands {
            AppCommands()
        }
        Settings {
            SettingsView(settings: userSettings)
        }
    }
}

The App for old systems just wraps CommonScene:

struct OldSystemApp: App {
    @StateObject var userSettings = UserSettings()
    @StateObject var viewModel = ViewModel()

    var body: some Scene {
        CommonScene(
            userSettings: userSettings,
            viewModel: viewModel
        )
    }
}

The App for macOS 13 adds the MenuBarExtra:

struct App13: App {
    @StateObject var userSettings = UserSettings()
    @StateObject var viewModel = ViewModel()
    
    var body: some Scene {
        CommonScene(
            userSettings: userSettings,
            viewModel: viewModel
        )
        
        MenuBarExtra() {
            MenuBarExtraContent(model: viewModel)
        } label: {
            MenuBarExtraLabel(model: viewModel)
        }
    }
}

Finally, write your own little type with a static void main() that runs the appropriate App type's main depending on whether macOS 13 is available, and add @main to that type:

@main
struct MyMain {
    static void main() {
        if #available(macOS 13.0, *) {
            App13.main()
        } else {
            OldSystemApp.main()
        }
    }
}
  • Related