6

我正在尝试使用 SwiftUI 2.0(应用程序结构)中引入的 SwiftUI 应用程序生命周期创建一个菜单栏应用程序,其中 SwiftUI 代码中的按钮和操作将更新状态栏项的文本。我正在创建一个状态栏项目,它将触发一个包含 SwiftUI 视图的弹出框。根据我的理解,最好的方法是将 statusBar 传递给 ContentView 的孩子,这将能够使用 statusBar 作为环境对象。但是,我遇到了一个我不明白的问题,即在应用程序委托中运行的代码在 SwiftUI 2.0 初始化函数中失败(原因不明)。我希望我已将下面的所有相关代码包含在一个能够触及问题核心的孤立示例中。

我有一个工作解决方案,使用NSApplicationDelegateAdaptor在 ContentView 之前初始化 statusBar 的位置,并且 statusBar 只是作为环境对象传递给 ContentView:

import SwiftUI
import AppKit

@main
struct test_status_bar_appApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        Settings{
            EmptyView()
        }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {
    var popover = NSPopover.init()
    var statusBar: StatusBarController?

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the Status Bar Item with the Popover
        statusBar = StatusBarController.init(popover)

        // Pass the Status Bar Item as an environment object to children of ContentView
        let contentView = ContentView()
            .environmentObject(statusBar!)

        popover.contentViewController = NSHostingController(rootView: contentView)
        popover.contentSize = NSSize(width: 360, height: 360)

        statusBar?.updateStatusBarText(text: "app delegate")
    }
}

这工作得很好。但是,它仍然使用较旧的应用程序委托。我试图在 SwiftUI 2.0 中使用应用程序结构的新 init() 函数,而不是将应用程序启动委托给应用程序(旧)委托。因此,我将上面的代码移到了 init() 函数中:

import SwiftUI
import AppKit

@main
struct test_status_bar_appApp: App {
    var popover = NSPopover.init()
    var statusBar: StatusBarController?

    init () {

        // ISSUE: putting line `statusBar = StatusBarController.init(popover)`
        // (so that it can be passed in as an environment variable)
        // causes error
        statusBar = StatusBarController.init(popover)

        let contentView = ContentView()
            .environmentObject(statusBar!)

        popover.contentViewController = NSHostingController(rootView: contentView)
        popover.contentSize = NSSize(width: 360, height: 360)

        statusBar?.updateStatusBarText(text: "init before")
    }

    var body: some Scene {
        Settings{
            EmptyView()
        }
    }
}

同样的序列(在应用程序委托中工作)在应用程序结构的初始化中失败,在包含statusBar = NSStatusBar.init()

错误本身显示为Thread 1: hit program assert,并带有其他错误详细信息:

Assertion failed: (CGAtomicGet(&is_initialized)), function CGSConnectionByID, file /System/Volumes/Data/SWE/macOS/BuildRoots/e90674e518/Library/Caches/com.apple.xbs/Sources/SkyLight/SkyLight-588.1/SkyLight/Services/Connection/CGSConnection.mm, line 133.
Assertion failed: (CGAtomicGet(&is_initialized)), function CGSConnectionByID, file /System/Volumes/Data/SWE/macOS/BuildRoots/e90674e518/Library/Caches/com.apple.xbs/Sources/SkyLight/SkyLight-588.1/SkyLight/Services/Connection/CGSConnection.mm, line 133.
(lldb)

但是,在设置弹出框的尺寸后放置 StatusBarController 的初始化确实可以在菜单栏中创建状态栏项......但不幸的是,这种方式无法作为 environmentObject 传递给 ContentView。代码如下:

import SwiftUI
import AppKit

@main
struct test_status_bar_appApp: App {
    var popover = NSPopover.init()
    var statusBar: StatusBarController?

    init () {
        let contentView = ContentView()
//            .environmentObject(statusBar!)

        popover.contentViewController = NSHostingController(rootView: contentView)
        popover.contentSize = NSSize(width: 360, height: 360)

        // BUT... putting the status bar HERE works,
        // but is sadly not able to be passed
        // as an environmentObject into the ContentView above
        statusBar = StatusBarController.init(popover)
        statusBar?.updateStatusBarText(text: "init after")
    }

    var body: some Scene {
        Settings{
            EmptyView()
        }
    }
}

任何人都能够解释发生了什么,为什么会发生这种情况,也许如何解决它?目前,我确实有一个可行的解决方案,我可以通过旧的应用程序委托将 statusBar 作为 environmentObject 传递给 ContentView。但理想情况下,我会使用 SwiftUI 生命周期中 app 结构的较新 init() 函数,因为这似乎是在新结构中执行此操作的方法,同时保留将 statusBar 作为 environmentObject 传递给 ContentView 的能力。

其他相关代码如下。这是状态栏控制器:

class StatusBarController: ObservableObject {
    private var statusBar: NSStatusBar
    private var statusItem: NSStatusItem
    private var popover: NSPopover
    
    init(_ popover: NSPopover) {
        self.popover = popover

        statusBar = NSStatusBar.init()
        statusItem = statusBar.statusItem(withLength: 80)
        
        if let statusBarButton = statusItem.button {
            statusBarButton.wantsLayer = true
            statusBarButton.layer?.masksToBounds = true
            statusBarButton.layer?.cornerRadius = 5
        }
    }
    
    func updateStatusBarText(text: String) {
        statusItem.button?.title = text
    }
}

内容视图:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var statusBar: StatusBarController
    
    var body: some View {
        VStack{
            Text("Hello, world!").padding()
            Button("Ok", action: {
                print("clicked button in swiftui")
                // NOTE: example of trying to use statusBar environment object
                // statusBar.updateStatusBarText(text: "IT's WORKING!!!")
            }).padding()
        }.frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

空视图:

struct EmptyView: View {
    var body: some View {
        Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
    }
}
4

0 回答 0