在 macOS Cocoa 应用程序中有两个相同的 NSOutlineViews 并排,有没有办法在两者之间同步展开/折叠项目?

In a macOS Cocoa Application that has two identical NSOutlineViews side by side, is there a way to expand / collapse items in sync between the two?

我正在 Swift 5.1 中开发 macOS Cocoa 应用程序。在主要 window 中,我有两个相同的 NSOutlineView,它们的内容完全相同。我想启用同步模式,如果两个 NSOutlineViews 之一中的项目为 expanded/collapsed,则相应的项目同时在另一个中为 expanded/collapsed。我试图通过在委托中实现 shouldExpandItem 和 shouldCollapseItem 来做到这一点。委托是两个 NSOutlineViews 的同一个对象,我有引用两个 NSOutlineViews 的出口来区分两者。问题是,如果我在 shouldExpandItem 中以编程方式调用 expandItem,则会再次为另一个 NSOutlineView 调用该方法,从而导致无限循环和堆栈溢出。我找到了一个可行的肮脏解决方案,方法是暂时将相关 NSOutlineView 的委托设置为 nil,expand/collapse 该项目,然后将委托设置回去。代码如下:

func outlineView(_ outlineView: NSOutlineView, shouldExpandItem item: Any) -> Bool {

    let filePath = (item as! FileSystemObject).fullPath

    let trueItem = item as! FileSystemObject

    trueItem.children = Array()

    do {
        let contents = try FileManager.default.contentsOfDirectory(atPath: filePath!)

        for (_, item) in contents.enumerated() {

            let entry = FileSystemObject.init()
            entry.fullPath = URL.init(fileURLWithPath: filePath!).appendingPathComponent(item).path
            if entry.exists {
                trueItem.children.append(entry)
            }
        }

    } catch {

    }

        if outlineView == self.leftOutlineView {
            self.rightOutlineView.delegate = nil;
            self.rightOutlineView.expandItem(item)
            self.rightOutlineView.delegate = self;
        } else {
            self.leftOutlineView.delegate = nil;
            self.leftOutlineView.expandItem(item)
            self.leftOutlineView.delegate = self;
    }

    return true

}

func outlineView(_ outlineView: NSOutlineView, shouldCollapseItem item: Any) -> Bool {

    if outlineView == self.leftOutlineView {
        self.rightOutlineView.delegate = nil;
        self.rightOutlineView.collapseItem(item)
        self.rightOutlineView.delegate = self;
    } else {
        self.leftOutlineView.delegate = nil;
        self.leftOutlineView.collapseItem(item)
        self.leftOutlineView.delegate = self;

    }

    return true
}

这似乎可行,但我担心这种方法可能会出错。暂时将代表设置为 nil 是一种可能的解决方案,还是我应该注意任何注意事项?您是否可以建议另一种模式来实现这一目标?谢谢

编辑:根据以下评论和答案

感谢我收到的答案/评论,我找到了最简单的解决方案。 可以实现 outlineViewDidExpand 和 outlineViewDidCollapse 并将同步逻辑放在那里,而不是在 outlineViewShouldExpand/Collapse 方法中实现同步逻辑。当以编程方式 expanding/collapsing 项目时,不会调用后一种方法,因此不存在无限循环或堆栈溢出的风险。

代码如下:

func outlineViewItemDidExpand(_ notification: Notification) {

    let outlineView = notification.object as! NSOutlineView

    let userInfo = notification.userInfo as! Dictionary<String, Any>

    let item = userInfo["NSObject"]

    if outlineView == self.leftOutlineView {
              self.rightOutlineView.animator().expandItem(item)
        } else {
              self.leftOutlineView.animator().expandItem(item)

    }
}

func outlineViewItemDidCollapse(_ notification: Notification) {

      let outlineView = notification.object as! NSOutlineView

      let userInfo = notification.userInfo as! Dictionary<String, Any>

      let item = userInfo["NSObject"]

      if outlineView == self.leftOutlineView {
                self.rightOutlineView.animator().collapseItem(item)
          } else {
                self.leftOutlineView.animator().collapseItem(item)

      }
  }

此外,现在,我不明白为什么项目的展开/折叠是动画的,这与我原来的方法不起作用。 我希望这对某人有帮助,这对我很有帮助。非常感谢。

我的应用程序执行类似的操作。我需要在 windows 中保持许多视图同步。其中一个视图是 NSOutlineView。 运行 我对 NSOutlineView 有一些怪癖,但我认为它们与同步无关。

不过,我确实采取了不同的方法。我没有操纵委托,而是有一个抑制某些动作的标志。在我的例子中,一个标志更有意义,因为许多其他视图都受到影响。但是,效果和你做的很相似。

我认为唯一的风险是在同步期间错过委托调用。假设你的逻辑不依赖于此,我相信你的方法会很好。

outlineView(_:shouldExpandItem:) 在项目展开之前调用,来回调用 expandItem() 会导致无限循环。 outlineViewItemDidExpand(_:) 在项目展开后调用,而 NSOutlineView.expandItem(_:) 在项目已经展开时不执行任何操作(已记录的行为)。展开左大纲视图时,右大纲视图的expandItem()确实调用了outlineViewItemDidExpand(_:),但左大纲视图的expandItem()不会再次调用outlineViewItemDidExpand(_:)

func outlineViewItemDidExpand(_ notification: Notification) {
    let outlineView = notification.object as! NSOutlineView
    let item = notification.userInfo!["NSObject"]
    if outlineView == self.leftOutlineView {
        self.rightOutlineView.animator().expandItem(item)
    } else {
        self.leftOutlineView.animator().expandItem(item)
    }
}