8

Smart KeyPathsNSArrayControllerを使用して、 の内容が変更されたときに通知を受け取る方法を教えてください。

に触発された

キー値の観察: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID12

スマート キーパス: Swift のキー値コーディングの改善: https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md

記事のサンプルコードを真似しました。

class myArrayController: NSArrayController {
  required init?(coder: NSCoder) {
    super.init(coder: coder)

    observe(\.content, options: [.new]) { object, change in
      print("Observed a change to \(object.content.debugDescription)")
    }
  }
}

しかし、それは機能していません。ターゲット オブジェクトに変更を加えても、通知は発生しません。

対照的に、以下にリストされている典型的な方法が機能しています。

class myArrayController: NSArrayController {
  required init?(coder: NSCoder) {
    super.init(coder: coder)

    addObserver(self, forKeyPath: "content", options: .new, context: nil)
  }

  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "content" {
      print("Observed a change to \((object as! myArrayController).content.debugDescription)")
    }
    else {
      super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
  }
}

新しい方法はよりエレガントに見えます。あなたの提案はありますか?

環境: Xcode 9 ベータ

  • macOS、ココア アプリ、Swift 4
  • ドキュメントベースのアプリケーションを作成する
  • コア データを使用する

  • myArrayControllerのモードはEntity Nameで、次で準備されていますDocument.xcdatamodeld

  • myArrayController管理対象オブジェクト コンテキストは、モデル キー パスにバインドされています:representedObject.managedObjectContext
  • representedObjectのインスタンスが割り当てられますDocument
  • NSTableViewコンテンツ選択インデックス、およびソート記述子は、の対応にバインドされmyArrayControllerます。

環境の詳細: Binding managedObjectContext、Xcode 8.3.2、Storyboards、mac : https://forums.bignerdranch.com/t/binding-managedobjectcontext-xcode-8-3-2-storyboards-macos-swift/12284

編集済み

上記の例に関して、私はのmanagedObjectContext代わりにcontentを観察することに気が変わりましたNSArrayController

class myViewController: NSViewController {

  override func viewWillAppear() {
    super.viewWillAppear()

    let n = NotificationCenter.default
    n.addObserver(self, selector: #selector(mocDidChange(notification:)),
                  name: NSNotification.Name.NSManagedObjectContextObjectsDidChange,
                  object: (representedObject as! Document).managedObjectContext)
    }
  }

  @objc func mocDidChange(notification n: Notification) {
    print("\nmocDidChange():\n\(n)")
  }

}

その理由は、この 2 番目のアプローチが最初のアプローチよりも単純だからです。このコードは、表の行の追加と削除、および表のセルの値の変更など、必要な要件をすべてカバーしています。欠点は、アプリケーション内の別のすべてのテーブルの変更とさらに別のエンティティの変更によって通知が発生することです。しかし、そのような通知は面白くありません。しかし、それは大したことではありません。

対照的に、最初のアプローチはより複雑になります。

追加と削除の場合、2 つの関数を監視または実装する必要がありcontentますNSArrayController

func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
func tableView(_ tableView: NSTableView, didRemove rowView: NSTableRowView, forRow row: Int)

からNSTableViewDelegate。は に接続されていNSTableViewます。delegateNSViewController

少し驚くべきことに、両方のtableView()関数が非常に頻繁に呼び出されます。たとえば、テーブルに 10 行ある場合、行を並べ替えると、10 回のdidRemove呼び出しの後に10 回の呼び出しが続きdidAddます。行を 1 つ追加すると、10 回のdidRemove呼び出しが発生し、さらに 11 回のdidAdd呼び出しが発生します。それはあまり効率的ではありません。

変更には、必要になります

func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool

からNSControlTextEditingDelegate、 のスーパーNSTableViewDelegateNSTextField各テーブル列はすべて、その を介して接続する必要がありNSViewControllerますdelegate

さらに、残念ながら、これcontrol()はテキストの編集が完了した直後に呼び出されますが、実際の値NSArrayControllerが更新される前に呼び出されます。つまり、やや無駄です。最初のアプローチではまだ良い解決策が見つかりません。

とにかく、この投稿の主なトピックはSmart KeyPaths の使い方です。:-)

編集2

両方使うつもり

  1. ... のプロパティcontentを観察する最初のものNSArrayController
  2. ... 2 番目のNotification投稿者を観察するNSManagedObjectContext

1 は、ユーザーがマスター/詳細ビューを変更したときのもので、 に変更を加えませんNSManagedObjectContext

2 は、ユーザーが変更を加えたときのためのものです: 追加、削除、更新、および元に戻す、マウスイベントを伴わないCommand-Z 。

現時点では、 のバージョンaddObserver(self, forKeyPath: "content", ...が使用されます。この投稿の問題が解決したら、次のバージョンに切り替えますobserve(\.content, ...

ありがとう。

編集3

コード 2.observing aNotificationは完全に新しいものに置き換えられました。

4

1 に答える 1