微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在 SwiftUI 应用程序生命周期中将 statusBar 传递给 popover 中的 contentView?

如何解决如何在 SwiftUI 应用程序生命周期中将 statusBar 传递给 popover 中的 contentView?

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

我有一个使用 NSApplicationDelegateAdaptor 的工作解决方案,其中 statusBar 在 ContentView 之前初始化,并且 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() 的行上的 StatusBarController 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)),line 133.
(lldb)

但是,在设置弹出窗口的尺寸之后放置 StatusBarController 的初始化确实可以在菜单栏中创建一个状态栏项目...但不幸的是,以一种无法作为环境对象传递给 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 生命周期中应用程序结构的较新 init() 函数,因为这似乎是在新结构中执行此操作的方式,同时保留将 statusBar 作为 environmentObject 传递给 ContentView 的能力。

其他相关代码如下。这是 StatusBarController:

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@*/)
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。