Я разрабатываю приложение 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)
}
}
Более того, теперьЯ не могу понять, почему, расширение / развал предметов анимирован, что не работало с моим первоначальным подходом. Я надеюсь, что это может быть полезно для кого-то, это было очень полезно для меня. Большое спасибо.