【问题标题】:Using NSTreeController with NSOutlineView将 NSTreeController 与 NSOutlineView 一起使用
【发布时间】:2017-01-04 22:25:32
【问题描述】:

我正在尝试(不成功)构建一个由 TreeController 控制的 NSOutlineView。我已经阅读了一堆教程,但是它们都在开始任何事情之前预先加载了数据,这对我不起作用。

我有一个简单的设备类:

import Cocoa
class Device: NSObject {
    let name : String
    var children = [Service]()
    var serviceNo = 1
    var count = 0

    init(name: String){
        self.name = name
    }

    func addService(serviceName: String){
        let serv = "\(serviceName) # \(serviceNo)"
        children.append(Service(name: serv))
        serviceNo += 1
        count = children.count
    }

    func isLeaf() -> Bool {
        return children.count < 1
    }
}

我还有一个更简单的“服务”类:

import Cocoa
class Service: NSObject {

    let name: String

    init(name: String){
        self.name = name
    }
}

最后,我有了 ViewController:

class ViewController: NSViewController {

    var stepper = 0

    dynamic var devices = [Device]()
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    @IBAction func addDeviceAction(_ sender: Any) {
        let str = "New Device #\(stepper)"
        devices.append(Device(name: str))
        stepper += 1
        print("Added Device: \(devices[devices.count-1].name)")    
    }

    @IBAction func addService(_ sender: Any) {
        for i in 0..<devices.count {
            devices[i].addService(serviceName: "New Service")
       }
    }
}

显然我有 2 个按钮,一个用于添加“设备”,一个用于为每个设备添加“服务”。

不能做到的是这些数据中的任何一个都显示在 NSOutlineView 中。我已将 TreeController 的 Object Controller 属性设置为 Mode: Class 和 Class: Device,并且没有设置 Children、Count 或 Leaf 属性,我得到(可以预见):

2017-01-04 17:20:19.337129 OutlineTest[12550:1536405] 警告:[对象类:设备] childrenKeyPath 不能为 nil。要消除此日志消息,请在 Interface Builder 中设置 childrenKeyPath 属性

如果我随后将 Children 属性设置为 'children',事情会变得非常糟糕:

2017-01-04 17:23:11.150627 OutlineTest[12695:1548039] [常规] [ addObserver:forKeyPath:options:context:] 不受支持。关键路径:儿童

我要做的只是将 NSOutlineView 设置为从 NSTreeController 获取输入,以便在将新的“设备”添加到 devices[] 数组时,它会显示在大纲视图中。

如果有人能在这里指出正确的方向,我将不胜感激。

非常感谢 Warren 所做的非常有帮助的工作。我已经(大部分)工作了。除了 Warren 的建议,我还需要做几件事:

为 Tree Controller 设置数据存储

将 OutlineView 绑定到 TreeController

将 Column 绑定到 TreeController

将 TableView 单元格绑定到表格单元格视图(是的,真的)

所有都完成后,我不得不对实际的数据存储区进行一些操作:

   var name = "Bluetooth Devices Root"
var deviceStore = [Device]()
@IBOutlet var treeController: NSTreeController!
@IBOutlet weak var outlineView: NSOutlineView!


override func viewDidLoad() {
    super.viewDidLoad()
    deviceStore.append(Device(name: "Bluetooth Devices"))
    self.treeController.content = self
}

override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
}

@IBAction func addDeviceAction(_ sender: Any) {
    if(deviceStore[0].name == "Bluetooth Devices"){
        deviceStore.remove(at: 0)
    }

事实证明,Root 在开始时不能没有孩子,至少据我所知。添加孩子后,我可以删除占位符值,并且树似乎(大部分)可以按我的意愿工作。另一件事是,每当数据发生变化时,我都必须重新加载数据并重新显示大纲:

 outlineView.reloadData()
 outlineView.setNeedsDisplay()

没有它,什么都没有。我仍然没有正确更新数据(请参阅 Warren 答案下方的 cmets),但我快到了。

【问题讨论】:

  • 您可以尝试完全设置数组,而不是插入,这样 KVO 通知应该正确触发。
  • 我实际上做不到。实际上,我会扫描网络设备并在找到它们时添加它们,一旦这有效,我无法预加载它们,并且必须在找到它们时插入它们。
  • 你可以用一个不可变的数组来做到这一点,而不是写devices.append(Device(...)),你写devices = devices + [Device(...)],因此devices的值在每次找到新设备时都会改变。
  • 或者,您可以使用树控制器进行插入。

标签: swift macos nsoutlineview nstreecontroller


【解决方案1】:

显而易见,NSTreeController 管理着一个对象树,所有这些对象都需要回答以下三个问题/请求。

  • 你是一片叶子,即你没有孩子吗? = leafKeyPath
  • 如果你不是一片叶子,你有几个孩子? = countKeyPath
  • 把你的孩子给我! = childrenKeyPath

在 IB 中或以编程方式设置它们很简单。分别是一组相当标准的属性。

  • isLeaf
  • childCount
  • children

但它完全是任意的,可以是回答这些问题的任何属性集。

我通常会设置一个名为 TreeNode 之类的协议,并让我的所有对象都符合它。

@objc protocol TreeNode:class { 
    var isLeaf:Bool { get }
    var childCount:Int { get }
    var children:[TreeNode] { get } 
}

对于您的Device 对象,您使用isLeafchildren 回答3 个问题中的2 个,但不要回答childCount 问题。

您的设备的孩子是Service 对象,他们没有回答任何问题,这也是您遇到异常的部分原因。

所以要修复你的代码,一个可能的解决方案是......

服务对象

class Service: NSObject, TreeNode {

    let name: String
    init(name: String){
        self.name = name
    }

    var isLeaf:Bool {
        return true
    }
    var childCount:Int {
        return 0
    }
    var children:[TreeNode] {
        return []
    }
}

设备对象

class Device: NSObject, TreeNode {
    let name : String
    var serviceStore = [Service]()

    init(name: String){
        self.name = name
    }

    var isLeaf:Bool {
        return serviceStore.isEmpty
    }
    var childCount:Int {
        return serviceStore.count
    }
    var children:[TreeNode] {
        return serviceStore
    }

}

从 MVC 的角度来看,这是一件可怕的事情,但对于这个答案来说很方便。根对象。

class ViewController: NSViewController, TreeNode {

    var deviceStore = [Device]()
    var name = "Henry" //whatever you want to name your root

    var isLeaf:Bool {
        return deviceStore.isEmpty
    }
    var childCount:Int {
        return deviceStore.count
    }
    var children:[TreeNode] {
        return deviceStore
    }

}

所以你需要做的就是设置你的 treeController 的内容。假设您的ViewController 中有一个IBOutlet

class ViewController: NSViewController, TreeNode {

    @IBOutlet var treeController:NSTreeController!
    @IBOutlet var outlineView:NSOutlineView!


    override func viewDidLoad() {
        super.viewDidLoad()
        treeController.content = self
    } 

现在,每次您附加设备或添加服务时,只需在 outlineView 上调用 reloadItem(您还需要一个插座)

@IBAction func addDeviceAction(_ sender: Any) {
        let str = "New Device #\(stepper)"
        devices.append(Device(name: str))
        stepper += 1
        print("Added Device: \(devices[devices.count-1].name)")
        outlineView.reloadItem(self, reloadChildren: true)    
    }

这是基础知识,应该可以帮助您入门,但 NSOutlineViewNSTreeController 的文档有更多信息。

编辑

除了上述内容之外,您还需要将大纲视图绑定到树控制器。

首先确保您的大纲视图处于查看模式。

接下来将表格列绑定到树控制器上的arrangedObjects

最后将文本单元格绑定到相关的键路径。在您的情况下,它是 nameobjectValue 是对单元格中对象的引用。

【讨论】:

  • 谢谢沃伦!这很棒,让我摆脱了例外。不幸的是,我认为我在某个地方错过了一步,因为我的 OutlineView 中仍然一无所获。我确定在 IB 或其他地方有一些我错过了的设置。
  • 谢谢沃伦!那当然更好。 OutlineView 现在至少加载根对象。它仍然不会在创建新对象时添加任何新对象。希望我今天能找到那部分。我有一个从 ArrayController 更新内容的 tableView,它非常简单易行。这个 TreeController/OutlineView 看起来像是一个严重损坏的安排!
  • 所以还有一些其他的步骤,我几乎在那里。我已经在原始问题中添加了一些屏幕截图,其中包含我必须采取的额外步骤。此外,我必须向 deviceStore 添加一个虚拟项目,否则 nothing 将永远显示。完成后,我可以添加新项目并将它们添加到 OutlineView。警告:它们仅在展开大纲之前添加到 OutlineView。展开根项目后,视图中不再添加任何项目。我打电话给outlineView.reloadData() 然后outlineView.setNeedsDisplay() 无效。
  • 这是一个神奇的解决方案:treeController.rearrangeObjects() 必须在任何数据添加到数据存储后调用。打电话给那个,然后是reloadData(),然后是setNeedsDisplay(),鲍勃就是你的叔叔!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多