sil*_*fer 7 macos nsstatusbar swift swiftui
我正在尝试使用 SwiftUI 2.0(应用程序结构)中引入的 SwiftUI 应用程序生命周期创建一个菜单栏应用程序,其中 SwiftUI 代码中的按钮和操作将更新状态栏项目的文本。我正在创建一个状态栏项目,它将触发一个包含 SwiftUI 视图的弹出窗口。根据我的理解,最好的方法是将 statusBar 传递给 ContentView 的子级,后者将能够使用 statusBar 作为环境对象。但是,我遇到一个我不明白的问题,即在应用程序委托中工作的代码在 SwiftUI 2.0 init 函数中失败(不清楚原因)。我希望我已将下面所有相关的代码包含在一个单独的示例中,以触及问题的核心。
我有一个工作解决方案,使用NSApplicationDelegateAdaptorstatusBar 在 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")
}
}
Run Code Online (Sandbox Code Playgroud)
这工作相当不错。但是,它仍然使用旧的应用程序委托。我试图在 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()
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个相同的序列(在应用程序委托中工作)在应用程序结构的 init 中失败,在包含以下行的 StatusBarController init 中失败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)
Run Code Online (Sandbox Code Playgroud)
但是,在设置弹出窗口的尺寸之后放置 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()
}
}
}
Run Code Online (Sandbox Code Playgroud)
谁能解释发生了什么事,为什么会发生这种情况,以及如何解决它?目前,我确实有一个可行的解决方案,我可以通过旧的应用程序委托将 statusBar 作为环境对象传递到 ContentView 中。但理想情况下,我会使用 SwiftUI 生命周期中 app 结构的较新 init() 函数,因为这似乎是在新结构中执行此操作的方法,同时保留将 statusBar 作为环境对象传递给 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
}
}
Run Code Online (Sandbox Code Playgroud)
内容查看:
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()
}
}
Run Code Online (Sandbox Code Playgroud)
空视图:
struct EmptyView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
Run Code Online (Sandbox Code Playgroud)
尝试将代码放入 DispatchQueue.main.async { code }
例如:
DispatchQueue.main.async {
let statusBar = NSStatusBar.system
var statusBarItem : NSStatusItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.image = NSImage(systemSymbolName: "hare", accessibilityDescription: nil)
statusBarItem.button?.imageScaling = .scaleProportionallyDown
statusBarItem.button?.action = #selector(self.openMenuBar(sender:))
statusBarItem.button?.target = self
statusBarItem.button?.sendAction(on: [.leftMouseDown])
}
Run Code Online (Sandbox Code Playgroud)
为了完整起见,在此示例中,单击处理程序应按以下方式编写:
@objc func openMenuBar(sender: Any) {
print("Open MenuBar")
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
730 次 |
| 最近记录: |