NSViewControllerでNSMenuItemから実行可能なdeleteを実装する
自分のツイートをより詳細化した投稿です。
例えば「NSTableViewで⌫(デリートキー)を押した際に行を削除したい」というのは、割と良くあるケースだと思うので、これを試してみることにします。
Storyboardを利用を選択してMac Appのプロジェクトを作成すると、テンプレートでは以下のようにアプリケーションメニューが生成されており、deleteのNSMenuItemも最初から存在しています。
しかし、iOSのUIResponderと違い、cut・copy・paste・deleteなどの標準アクションはNSResponderには実装されていないため、NSViewControllerでoverrideして実装というのが実現できません。どうしたらいいのか困ります。
(より詳細に言うと、UIResponderで実装してあるのはUIResponderStandardEditActionsプロトコルで定義して適合してあるメソッドですが、AppKitにはそのようなプロトコルはありませんし、NSResponderで直接実装されているということもありません。)
こちらのStackoverflowの回答を見ると「Deleteはfirst responderに接続されているから、Deleteアクションに⌫を割り当てて、ViewControllerで@IBAction func delete(_ sender: Any?)を実装すれば実現できるよ」と書いてありますが本当でしょうか?試してみます。
実装コードは下記のみです。
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
// 追加実装したメソッド
@IBAction func delete(_ sender: Any?) {
print("\(#function) is called")
}
}
実行してみると、何もないViewControllerが表示されていますが、Deleteが実行できるようになっています。
Deleteキーを押して実行すると、コンソールでprintが呼び出されたことを確認できます。
上記のdelete:メソッドで、NSTreeControllerやNSArrayControllerを使って行を削除する実装を行えば、冒頭の目的は果たせそうです。
まだdeleteしか試していませんが、おそらく他の標準編集アクションについても、同様の処理が実現できるでしょう。
NSMenuItemのdisable/enableをVCの状態によって切り替えたい場合
おそらく、もっとも自然な方法はお使いのViewControllerでNSMenuItemValidation.validateMenuItem(_:)を実装することです。
内部実装を推察すると、Responder Chainを辿りながらvalidateMenuItemをチェックしているのでないだろうか。と思ったので、とりあえずview.window.validateMenuItem(menuItem)を呼び出すようなことはせず、普通にfalseを返しました。また検証してみて分かったことがあれば更新します。
extension ViewController: NSMenuItemValidation {
// 標準編集アクションの利用可能状態の表明
// Responder Chainの設計にして、自分の処理が終わったら次に適合しているNSWindowに処理を回す.
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
// このサンプルでは選択している行が空の場合はfalseを返し(disable), それ以外ではtrueを返す(enable)
if menuItem.action == #selector(delete) {
return !treeController.selectionIndexPaths.isEmpty
}
return false
}
}
おわりです。
Discussion