英文:
macOS Menubar popover appearing in the wrong place on launch
问题
我有一个 macOS 菜单栏应用程序,我希望在应用程序启动后显示弹出窗口。我遇到的问题是,虽然它确实显示了弹出窗口,但它将其放在屏幕的左下角,而不是直接在菜单栏图标下面。
是否有一种方法可以让它自动将弹出窗口放在正确的位置?
这是我的代码:
import Foundation
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var statusBarItem: NSStatusItem!
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
let iconName = UserDefaults.standard.string(forKey: "selectedIcon") ?? "Color"
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.image = NSImage(named: iconName)
statusBarItem.button?.image?.size = NSSize(width: 18, height: 18)
let contentView = ContentView()
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
statusBarItem.button?.action = #selector(togglePopover(_:))
statusBarItem.button?.target = self
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.statusBarItem.button?.window?.layoutIfNeeded()
self.statusBarItem.button?.performClick(nil)
}
}
@objc func togglePopover(_ sender: AnyObject?) {
if let button = self.statusBarItem.button {
if popover.isShown {
popover.performClose(sender)
} else {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
英文:
I have a macOS menubar application and I want it to display the popover after the app has launched. The problem I'm having is that, while it does display the popover, it places it in the bottom-left corner of the screen instead of directly beneath the menubar icon.
Is there a way to make it place the popover in the correct location automatically?
Here is my code:
import Foundation
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var statusBarItem: NSStatusItem!
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
let iconName = UserDefaults.standard.string(forKey: "selectedIcon") ?? "Color"
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.image = NSImage(named: iconName)
statusBarItem.button?.image?.size = NSSize(width: 18, height: 18)
let contentView = ContentView()
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
statusBarItem.button?.action = #selector(togglePopover(_:))
statusBarItem.button?.target = self
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.statusBarItem.button?.window?.layoutIfNeeded()
self.statusBarItem.button?.performClick(nil)
}
}
@objc func togglePopover(_ sender: AnyObject?) {
if let button = self.statusBarItem.button {
if popover.isShown {
popover.performClose(sender)
} else {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
答案1
得分: 1
为了使其正确放置,您必须在NSPopover
上设置contentSize
。而且,显然要比您设置的延迟稍微等待更长时间。例如,这样做:
let contentSize: CGSize = .init(width: 200, height: 300) // <-- 这里
@main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var statusBarItem: NSStatusItem!
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.image = NSImage(systemSymbolName: "pencil", accessibilityDescription: nil)
statusBarItem.button?.image?.size = NSSize(width: 18, height: 18)
let contentView = ContentView()
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
popover.contentSize = contentSize // <-- 这里
statusBarItem.menu = nil
statusBarItem.button?.action = #selector(togglePopover(_:))
statusBarItem.button?.target = self
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // <-- 这里
self.statusBarItem.button?.window?.layoutIfNeeded()
self.statusBarItem.button?.performClick(nil)
}
}
@objc func togglePopover(_ sender: NSStatusBarButton?) {
if let button = self.statusBarItem.button {
if popover.isShown {
popover.performClose(sender)
} else {
popover.show(relativeTo: sender!.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
struct ContentView: View {
var body: some View {
Text("Hello, world")
.frame(width: contentSize.width, height: contentSize.height) // <-- 这里
}
}
您还可以动态读取SwiftUI视图的大小,并根据该大小设置内容大小。请参阅https://www.fivestars.blog/articles/swiftui-share-layout-information/ 以获取示例。
英文:
For it to get placed correctly, you must set the contentSize
on the NSPopover
. And, apparently wait slightly longer than the delay you've set. For example, this works:
let contentSize: CGSize = .init(width: 200, height: 300) // <-- Here
@main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var statusBarItem: NSStatusItem!
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.image = NSImage(systemSymbolName: "pencil", accessibilityDescription: nil)
statusBarItem.button?.image?.size = NSSize(width: 18, height: 18)
let contentView = ContentView()
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
popover.contentSize = contentSize // <-- Here
statusBarItem.menu = nil
statusBarItem.button?.action = #selector(togglePopover(_:))
statusBarItem.button?.target = self
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // <-- Here
self.statusBarItem.button?.window?.layoutIfNeeded()
self.statusBarItem.button?.performClick(nil)
}
}
@objc func togglePopover(_ sender: NSStatusBarButton?) {
if let button = self.statusBarItem.button {
if popover.isShown {
popover.performClose(sender)
} else {
popover.show(relativeTo: sender!.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
struct ContentView: View {
var body: some View {
Text("Hello, world")
.frame(width: contentSize.width, height: contentSize.height) // <-- Here
}
}
You could also dynamically read the size of the SwiftUI view and set the content size based on that. See https://www.fivestars.blog/articles/swiftui-share-layout-information/ for example.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论