macOS菜单栏弹出窗口在启动时出现在错误的位置。

huangapple go评论137阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年5月29日 02:08:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76352935.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定