MacアプリのNSMenuが変な場所に表示される
https://gyazo.com/0a8d8de6dade4316bd01eeea23b91117
Macアプリ でこちら↑のようなメニューを NSMenu で表示すると、なぜかボタンの位置ではなく、画面の左下に表示されてしまいました。
調べてみると、 popUpContextMenu(_:with:for:) に渡している NSEvent がおかしいようでした。
ボタンの位置にメニューが表示されるときは、NSEvent の EventType が leftMouseUp になっていました。それに対して、画面の左下に表示されるときは、 EventType が systemDefined になっていました。
(EventTypeについては、 EventTypeのリファレンス に一覧があります)
こちらの記事 によると、 popUpContextMenu(_:with:for:) は 引数の NSEvent の mouseLocation プロパティの位置にメニューを表示するそうです。
メニューが左下に表示されるときは、EventType が systemDefined になっていたため、 mouseLocation が取れずに、原点座標にメニューが表示されたようです。
なぜ、 NSEvent が systemDefined になっていたかというと、どうやら別スレッドを通していたからのようです。
ボタンをクリック→別スレッドでメニュー表示データを取得する処理を実行→メインスレッドでメニュー表示処理を呼び出す
という処理の流れなので、別スレッドで処理をしている間に、マウスクリックイベントが電子の海に流れていったのでしょう。
対応策
というわけで、対応策を考えてみました。
code: Swift
class ViewController: NSViewController {
private var lastEvent: NSEvent?
@IBAction func clickButton(_ sender: NSButton) {
lastEvent = NSApplication.shared.currentEvent
// メニューを表示するための前処理
}
func presentMenu() {
guard let event = lastEvent else {
return
}
let menu = NSMenu(title: "ボタンメニュー")
menu.delegate = self
menu.addItem(NSMenuItem(title: "設定", action: #selector(clickSetting(sender:)), keyEquivalent: ""))
NSMenu.popUpContextMenu(menu, with: event, for: menuButton)
}
}
extension ViewController: NSMenuDelegate {
func menuDidClose(_ menu: NSMenu) {
lastEvent = nil
}
}
ボタンをクリックしたときに、 NSEvent をインスタンス変数 lastEvent に保存します。
そして、 popUpContextMenu(_:with:for:) の引数に lastEvent を渡して、ボタンの位置にメニューを表示します。
あとは、NSMenuDelegate を設定して、 menuDidClose で lastEvent を nil にしています。
ベストプラクティスかはわかりませんが、いまのところは問題なく動いています。