在多个故事板场景中使用核心数据和 Cocoa 绑定

Using Core Data and Cocoa Bindings in multiple storyboard scenes

我有一个 macOS 应用程序——不是基于文档的——它使用 Cocoa 绑定、核心数据和故事板。数据模型很简单...

List
  Name
  Players; to many relationship to Player

Player
  Name
  Lists; to-many relationship to List

情节提要具有以下布局...

Window Controller
  Split View Controller
    View Controller
      Table View
    View Controller
      Table View
    View Controller
      Label

我想弄清楚的是如何在三个视图控制器之间正确共享托管 object 上下文,并使两个 table 视图和标签保持同步。使用 this question 中的答案,我得到了 几乎 功能,尽管略有不同,因为我有 many-to-many 关系,所以需要额外的 table 视图。

我已经设置了一个中间控制器 object,它引用了托管的 object 上下文,两个用于保持各种数组控制器同步的索引数组,以及初始排序描述符.

class Controller: NSObject {
  @objc var moc = ...
  @objc var listSelectionIndexes = IndexSet()
  @objc var playerSelectionIndexes = IndexSet()
  @objc var sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
} 

然后我有一个 NSSplitViewController 的子类,用于将此控制器注入三个视图控制器...

class SplitViewController: NSSplitViewController {
    private let controller = Controller()

    override func awakeFromNib() {
        super.awakeFromNib()
        children.forEach {
            [=14=].representedObject = controller
        }
    }
}

在 left-most 控制器中,我有一个单独的数组控制器,设置为保留其选择并准备其内容,并具有以下绑定...

[Entity: List]
Selection Indexes ~> View Controller.self.representedObject.listSelectionIndexes
Managed Object Context ~> View Controller.self.representedObject.moc

和对应的table视图...

Content ~> Array Controller.arrangedObjects
Selection Indexes ~> Array Controller.selectionIndexes
Sort Descriptors ~> Array Controller.sortDescriptors

在中间的控制器中,我有两个数组控制器,它们都设置为保留它们的选择并准备它们的内容,并具有以下绑定...

[Array Controller 1; Entity: List]
Selection Indexes ~> View Controller.self.representedObject.listSelectionIndexes
Managed Object Context ~> View Controller.self.representedObject.moc

[Array Controller 2; Entity: Player]
Content Set ~> Array Controller 1.selection.players
Selection Indexes ~> View Controller.self.representedObject.playerSelectionIndexes
Sort Descriptors ~> View Controller.self.representedObject.sortDescriptors
Managed Object Context ~> View Controller.self.representedObject.moc

和相应的table视图绑定到数组控制器2...

Content ~> Array Controller 2.arrangedObjects
Selection Indexes ~> Array Controller 2.selectionIndexes
Sort Descriptors ~> Array Controller 2.sortDescriptors

最后,right-most 视图控制器,我有一个单独的数组控制器,设置为准备其内容但 保留选择,并具有以下绑定。 ..

[Entity: Player]
Selection Indexes ~> View Controller.self.representedObject.playerSelectionIndexes
Sort Descriptors ~> View Controller.self.representedObject.sortDescriptors
Managed Object Context ~> View Controller.self.representedObject.moc

并且标签具有单一绑定...

Value ~> Array Controller.selection.name

您可以在此屏幕截图中看到一切 几乎 正常... 我说 almost 是因为如果我使用它的 header 对中间 table 进行排序,我会丢失 right-most 选择,即使保留了中间选择。 .. 此外,如果我单击已选择的行,它不会更新第三个窗格——我必须单击关闭然后再次打开才能更新它。选择任何其他行都会按预期工作。

如果我然后在 right-most 视图控制器中的数组控制器上启用 Preserve Selection a 选择被保留,但是这是错误的,因为看起来排序顺序没有同步......

如何解决此问题:right-most 视图控制器中的排序顺序和选择?

还有一个更普遍的问题,这真的是最好的方法吗?似乎有很多 plumbing – 几乎感觉像是 hack – 只是为了能够使用绑定在场景之间保持数据同步,而且我还担心使用成本多个阵列控制器都保存着相同的信息,尤其是在有数千条记录的情况下。

与其添加数组控制器来重建选定的列表或播放器,不如将选定的列表和播放器传输到另一个视图控制器更直接。当 table 视图中的选择发生变化时,需要一些代码来更改选定的列表和播放器。

class Controller: NSObject {
    @objc dynamic var moc = …
    @objc dynamic weak var selectedList : List?
    @objc dynamic weak var selectedPlayer : Player?
}

最左边的列表视图控制器:

视图控制器是 table 视图的委托。

class ListViewController: NSViewController, NSTableViewDelegate {

    @IBOutlet var arrayController: NSArrayController!

    func tableViewSelectionDidChange(_ notification: Notification) {
        let controller = representedObject as! Controller
        if arrayController.selectedObjects.count == 1,
            let list = arrayController.selectedObjects[0] as? List {
            controller.selectedList = list
        }
        else {
            controller.selectedList = nil
            controller.selectedPlayer = nil // tableViewSelectionDidChange isn't called on the Player table view
        }
    }

}

中间播放器视图控制器:

视图控制器是 table 视图的委托。

class PlayerViewController: NSViewController, NSTableViewDelegate {
    
    @IBOutlet var arrayController: NSArrayController!

    func tableViewSelectionDidChange(_ notification: Notification) {
        let controller = representedObject as! Controller
        if arrayController.selectedObjects.count == 1,
            let player = arrayController.selectedObjects[0] as? Player {
            controller.selectedPlayer = player
        }
        else {
            controller.selectedPlayer = nil
        }
    }
    
}

将Player数组控制器的Content Set绑定到View Controller.representedObject.selectedList.players

最右边的细节视图控制器:

将文本字段的值绑定到 View Controller.representedObject.selectedPlayer.name