Home > Back-end >  Sharing a @StateObject between iOS and macOS views which are not set up in my App protocol implement
Sharing a @StateObject between iOS and macOS views which are not set up in my App protocol implement

Time:09-28

I'm trying to make a multiplatform SwiftUI application that would run on iOS and macOS. On the last one, I would like the app to be visible only as a menu bar item (no Dock icon, no windows).

I decided to have two views:

  • MacSampleView() which is used on a macOS status bar popover via custom NSApplicationDelegate
  • SampleView() for iOS and other Apple platforms via traditional WindowGroup scene

In my implementation of the App I have the app delegate inside #if os(macOS) compiler directive to use it only on macOS.

All was good until I decided to have a @StateObject var configuration which I would like to share between platforms.

I have no trouble passing it as .environmentObject(configuration) to SampleView() in my WindowGroup, but when I want to pass it to the MacSampleView() which is created inside my NSApplicationDelegate — I simply can't access it there.

I'm new to Apple platforms programming and it feels like I'm wrong somewhere on the conceptual level, so appreciate any help with that.

@main
struct SampleApp: App {

    @StateObject var configuration = Config()
    
    #if os(macOS)
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    #endif

    var body: some Scene {
        #if os(macOS)
        Settings {
            EmptyView()
        }
        #else
        WindowGroup {
            SampleView().environmentObject(configuration)
        }
        #endif
}
class AppDelegate: NSObject, NSApplicationDelegate {
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // I struggle here! I want to pass `@StateObject var configuration` from the `SampleApp` to the `MacSampleView()` somehow.
        // It's not possible to simply call `.environmentObject(configuration)` because `configuration` is unavailable in the delegate.
        let macSampleView = MacSampleView()

        let popover = NSPopover()
        // ...
        popover.contentViewController = NSHostingController(rootView: macSampleView)
        // ...
    }
}

CodePudding user response:

As your Config is app-wide level single instance you can use shared object for both cases, like

class Config: ObservableObject {
   static let shared = Config()       // << this one !!

   // .. other code
}

@main
struct SampleApp: App {

    @StateObject var configuration = Config.shared    // << here !!
    
    #if os(macOS)
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    #endif

    var body: some Scene {
        #if os(macOS)
        Settings {
            EmptyView()
        }
        #else
        WindowGroup {
            SampleView().environmentObject(configuration)
        }
        #endif
}

class AppDelegate: NSObject, NSApplicationDelegate {
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let macSampleView = MacSampleView()
             .environmentObject(Config.shared)  // << same instance !!
        let popover = NSPopover()
        // ...
        popover.contentViewController = NSHostingController(rootView: macSampleView)
        // ...
    }
}
  • Related