В приложении MacOS Cocoa, в котором два идентичных NSOutlineView находятся рядом, есть ли способ развернуть / свернуть элементы синхронно между ними? - PullRequest
1 голос
/ 08 ноября 2019

Я разрабатываю приложение MacOS Cocoa в Swift 5.1. В главном окне у меня есть два идентичных NSOutlineViews, которые имеют одинаковое содержимое точно. Я хотел бы включить режим синхронизации, где, если элементы развернуты / свернуты в одном из двух NSOutlineView, соответствующий элемент развернут / свернут в другом одновременно. Я попытался сделать это, реализовав mustExpandItem и shouldCollapseItem в делегате. Делегат - это один и тот же объект для обоих NSOutlineView, и у меня есть розетки, которые ссылаются на два NSOutlineView для их различения. Проблема заключается в том, что, если я вызываю expandItem программно в shouldExpandItem, метод снова вызывается для другого NSOutlineView, что приводит к бесконечному циклу и переполнению стека. Я нашел грязное решение, которое работает, на мгновение установив делегат соответствующего NSOutlineView равным nil, разверните / сверните элемент, а затем верните делегат обратно. Код следующий:

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
}

Кажется, что это работает, но я боюсь, что что-то может пойти не так с этим подходом. Является ли установка делегата на мгновение равным нулю возможным решением, или я должен знать о любых предостережениях? Есть ли другой шаблон, который вы можете предложить для достижения этой цели? Спасибо

РЕДАКТИРОВАТЬ: В соответствии с нижеследующими комментариями и ответами

Я нашел самое простое решение благодаря полученным ответам / комментариям. Вместо реализации логики синхронизации в методах outlineViewShouldExpand / Collapse можно реализовать outlineViewDidExpand и outlineViewDidCollapse и разместить там логику синхронизации. последние методы не вызываются при программном развертывании / свертывании элементов, поэтому нет риска переполнения бесконечного цикла или стека.

Код выглядит следующим образом:

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)

      }
  }

Более того, теперьЯ не могу понять, почему, расширение / развал предметов анимирован, что не работало с моим первоначальным подходом. Я надеюсь, что это может быть полезно для кого-то, это было очень полезно для меня. Большое спасибо.

Ответы [ 2 ]

1 голос
/ 08 ноября 2019

Мое приложение делает что-то подобное. Мне нужно синхронизировать многие представления между окнами. Одним из таких представлений является NSOutlineView. Я столкнулся с несколькими причудами, связанными с NSOutlineView, но я не верю, что они связаны с синхронизацией.

Я, правда, придерживаюсь другого подхода. Вместо того, чтобы манипулировать делегатом, у меня просто есть флаг, который подавляет определенные действия. В моем случае флаг имеет больше смысла, так как это затрагивает множество других представлений. Но эффект очень похож на то, что вы делаете.

Я думаю, что единственный риск - пропустить вызов делегата во время синхронизации. Предполагая, что ваша логика не зависит от этого, я считаю, что ваш подход будет работать нормально.

0 голосов
/ 09 ноября 2019

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)
    }
}
...