🦋

SwiftUI: NSHostingViewでもAppDelegateをEnvironmentObjectで渡したい

2022/11/29に公開1

まず、SwiftUI AppでAppDelegateを使う方法は以下のような感じ。

@main
struct SampleApp: App {
    @NSApplicationDelegateAdaptor private var appDelegate: AppDelegate

    var body: some Scene {
        // 省略
    }
}

class AppDelegate: NSObject: NSApplicationDelegate, ObservableObject {
    func applicationDidFinishLaunching(_ notification: Notification) {}
}

次にViewからAppDelegateのもつプロパティーにアクセスしたい場合は以下のような感じになる。

@main
struct SampleApp: App {
    @NSApplicationDelegateAdaptor private var appDelegate: AppDelegate

    var body: some Scene {
        WindowGroup {
	    ContentView()
+	        .environmentObject(appDelegate)
	}
    }
}

class AppDelegate: NSObject: NSApplicationDelegate, ObservableObject {
+   @Published var showProgress: Bool = false

    func applicationDidFinishLaunching(_ notification: Notification) {}
}

struct ContentView: View {
+   @EnvironmentObject private var appDelegate: AppDelegate
    
    var body: some View {
        ProgressView()
            .progressViewStyle(.circular)
+           .opacity(appDelegate.showProgress ? 1.0 : 0.0)
    }
}

本題

このままではNSHostingViewだと@EnvironmentObjectではアクセスできない。

なので、Shared InstanceをAppDelegateに生やして、NSHostingViewを初期化するタイミングで.environmentObject()してあげる。

class AppDelegate: NSObject: NSApplicationDelegate, ObservableObject {
+   static private(set) var shared: AppDelegate! = nil

    @Published var showProgress: Bool = false

    func applicationDidFinishLaunching(_ notification: Notification) {
+       AppDelegate.shared = self
    }
}

class SampleWindow: NSWindow {
    init(frame: NSRect) {
        super.init(
            contentRect: frame,
            styleMask: [.closable, .miniaturizable, .resizable, .fullSizeContentView, .titled],
            backing: .buffered,
            defer: true
        )
	let sampleView = SampleView()
+	    .environmentObject(AppDelegate.shared)
	self.contentView = NSHostingView(rootView: sampleView)
    }
}

参考

SwiftUI 2 accessing AppDelegate

Discussion

KyomeKyome

ObservableObjectなViewModelからAppDelegateのプロパティにアクセスしたい場合は以下。

class SampleViewModel: ObservableObject {
    @ObservedObject var appDelegate = AppDelegate.shared

    func hoge() {
        Swift.print(appDelegate.showProgress)
    }
}

この場合、事前に.environmentObject(AppDelegate.shared)をしておく必要はない。