将NSTreeController与NSOutlineView

时间:2017-01-04 22:25:32

标签: swift macos nsoutlineview nstreecontroller

我尝试(不成功)构建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
    }
}

对于&#39;服务&#39;我还有一个更简单的课程:

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个按钮,一个添加了一个&#39;设备&#39;并且增加了一项服务&#39;到每个设备。

无法发生的事情是NSOutlineView中显示的任何数据。我已将TreeController的对象控制器属性设置为模式:类和类:设备,而不设置我得到的子项,计数或叶属性(可预测):

  

2017-01-04 17:20:19.337129 OutlineTest [12550:1536405]警告:[object class:Device] childrenKeyPath不能为nil。要消除此日志消息,请在Interface Builder

中设置childrenKeyPath属性

如果我将Children属性设置为&#39; children&#39;事情变得非常糟糕:

  

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

我尝试做的就是设置NSOutlineView以从NSTreeController获取输入,这样当一个新的&#39;设备&#39;被添加到devices []数组中,它显示在Outline视图中。

如果有人能指出我正确的方向,我将非常感激。

非常感谢沃伦的非常有益的工作。我(大部分)都在工作。除了沃伦的建议之外,我还需要做一些事情:

Set the datastore for the TreeController 设置树控制器的数据存储

Bind to the TreeView 将OutlineView绑定到TreeController

Bind the Column to the TreeController 将列绑定到TreeController

Bind the Table View Cell to the Table Cell View 将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的答案),但我几乎就在那里。

1 个答案:

答案 0 :(得分:6)

说明显而易见的是,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回答了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的内容。假设您IBOutlet中有一个ViewController

class ViewController: NSViewController, TreeNode {

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


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

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

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

这是基础,应该让你开始,但NSOutlineView&amp;的文档NSTreeController有更多信息。

修改

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

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

place outline view in view mode

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

bind column to tree controller

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

bind data to cell