Smart KeyPathsを使用して、NSArrayController
の内容が変更されたときに通知を受け取る方法を教えてください。
に触発された
Key-Value Observing: https://developer.Apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//Apple_ref/doc/uid/TP40014216-CH7-ID12
Smart KeyPaths:Swiftのより優れたKey-Valueコーディング: 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)
}
}
}
_
新しい方法はよりエレガントに見えます。あなたの提案はありますか?
環境:Xcode9ベータ
CoreDataを使用する
myArrayController
のモードはエンティティ名で、_Document.xcdatamodeld
_で作成されます
myArrayController
の管理対象オブジェクトコンテキストはモデルキーパスにバインドされています:_representedObject.managedObjectContext
_representedObject
にはDocument
のインスタンスが割り当てられます。NSTableView
'sContent、Selection Indexes、およびSort DescriptorsmyArrayController
の対応にバインドされています。環境の詳細:BindingmanagedObjectContext、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番目のアプローチが最初のアプローチよりも単純だからです。このコードは、テーブル行の追加と削除、およびテーブルセルの値の変更など、必要なすべての要件をカバーしています。欠点は、アプリ内でテーブルが1つおきに変更され、さらにエンティティが変更されるたびに通知が発生することです。ただし、このような通知は興味深いものではありません。しかし、それは大したことではありません。
対照的に、最初のアプローチはより複雑にする必要があります。
追加と削除の場合、content
のNSArrayController
を監視するか、2つの関数を実装する必要があります。
_func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
func tableView(_ tableView: NSTableView, didRemove rowView: NSTableRowView, forRow row: Int)
_
NSTableViewDelegate
から。 NSTableView
のdelegate
はNSViewController
に接続されています。
少し意外なことに、両方のtableView()
関数が頻繁に呼び出されます。たとえば、テーブルに10行ある状況では、行を並べ替えると、10回のdidRemove
呼び出しとそれに続く10回のdidAdd
呼び出しが発生します。 1つの行を追加すると、10回のdidRemove
呼び出しが発生し、次に11回のdidAdd
呼び出しが発生します。それはそれほど効率的ではありません。
変更するには、
_func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool
_
NSControlTextEditingDelegate
から、NSTableViewDelegate
のスーパー。各テーブル列のすべてのNSTextField
は、そのNSViewController
を介してdelegate
に接続する必要があります。
さらに、残念ながら、このcontrol()
は、テキストの編集が完了した直後ではなく、NSArrayController
の実際の値が更新される前に呼び出されます。つまり、やや役に立たない。私はまだ最初のアプローチで良い解決策を見つけていません。
[〜#〜]とにかく[〜#〜]、この投稿の主なトピックは、スマートキーパスの使用方法です。 :-)
編集済み2:
両方を使用します
content
のプロパティNSArrayController
を観察する...最初のプロパティNotification
がNSManagedObjectContext
によって投稿されているのを観察しています... 2番目のもの1は、ユーザーがマスター/詳細ビューを変更する場合に使用します。これにより、NSManagedObjectContext
は変更されません。
2は、ユーザーが変更を加えた場合に使用します。追加、削除、更新、および元に戻すCommand-Zで、マウスイベントは伴いません。
今のところ、_addObserver(self, forKeyPath: "content", ...
_のバージョンが使用されます。この投稿の質問が解決したら、_observe(\.content, ...
_のバージョンに切り替えます
ありがとう。
編集済み3:
Notification
を監視するコード2.は完全に新しいものに置き換えられました。
最初のコードについては、次のようになります。
_class myArrayController: NSArrayController {
private var mySub: Any? = nil
required init?(coder: NSCoder) {
super.init(coder: coder)
self.mySub = self.observe(\.content, options: [.new]) { object, change in
debugPrint("Observed a change to", object.content)
}
}
}
_
observe(...)
関数は、通知を受信する期間を示す存続期間を持つ一時的なオブザーバーを返します。返されたオブザーバーがdeinit
'dの場合、通知を受信しなくなります。あなたの場合、オブジェクトを保持したことがないため、メソッドスコープの直後にオブジェクトが停止しました。
さらに、手動で監視を停止するには、mySub
をnil
に設定します。これにより、古いオブザーバーオブジェクトが暗黙的にdeinit
sになります。