Dav*_*dgs 5 macos nsoutlineview nstreecontroller swift
我正在尝试(不成功)构建一个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
}
}
Run Code Online (Sandbox Code Playgroud)
我还有一个更简单的"服务"课程:
import Cocoa
class Service: NSObject {
let name: String
init(name: String){
self.name = name
}
}
Run Code Online (Sandbox Code Playgroud)
最后,我有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")
}
}
}
Run Code Online (Sandbox Code Playgroud)
显然我有2个按钮,一个添加"设备",另一个为每个设备添加"服务".
我无法实现的是NSOutlineView中显示的任何数据.我已经将TreeController的对象控制器属性设置为Mode:Class和Class:Device,并且没有设置我得到的Children,Count或Leaf属性(可预测):
2017-01-04 17:20:19.337129 OutlineTest [12550:1536405]警告:[对象类:设备] childrenKeyPath不能为零.要消除此日志消息,请在Interface Builder中设置childrenKeyPath属性
如果我然后将Children属性设置为'children'则会非常糟糕:
2017-01-04 17:23:11.150627 OutlineTest [12695:1548039] [General] [addObserver:forKeyPath:options:context:]不受支持.关键路径:儿童
我要做的就是设置NSOutlineView来从NSTreeController获取输入,这样当一个新的'Device'被添加到devices []数组时,它会显示在Outline视图中.
如果有人能指出我正确的方向,我将非常感激.
非常感谢沃伦的非常有益的工作.我(大部分)都在工作.除了沃伦的建议之外,我还需要做一些事情:
将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)
}
Run Code Online (Sandbox Code Playgroud)
事实证明,Root在开始时不能没有孩子,至少就我所知.一旦我添加了一个孩子,我就可以删除占位符值,并且树似乎可以正常工作(大部分).另一件事是我必须重新加载数据并在数据发生变化时重新显示大纲:
outlineView.reloadData()
outlineView.setNeedsDisplay()
Run Code Online (Sandbox Code Playgroud)
没有它,没有.我仍然没有正确更新数据(请参阅Warren的回答下面的评论),但我差不多了.
为了说明这一点,a NSTreeController管理一个对象树,所有这些都需要回答以下三个问题/请求.
leafKeyPathcountKeyPathchildrenKeyPath它很容易在IB或编程中设置它们.分别是一组相当标准的属性.
isLeafchildCountchildren但它完全是任意的,可以是回答这些问题的任何属性集.
我通常会设置一个类似的协议TreeNode,并使我的所有对象都符合它.
@objc protocol TreeNode:class {
var isLeaf:Bool { get }
var childCount:Int { get }
var children:[TreeNode] { get }
}
Run Code Online (Sandbox Code Playgroud)
对于你的Device对象,你回答2出3问题与isLeaf和children,但不回答这个问题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 []
}
}
Run Code Online (Sandbox Code Playgroud)
Device对象
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
}
}
Run Code Online (Sandbox Code Playgroud)
从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
}
}
Run Code Online (Sandbox Code Playgroud)
所以你需要做的就是设置treeController的内容.让我们假设你有一个IBOutlet在你给它ViewController.
class ViewController: NSViewController, TreeNode {
@IBOutlet var treeController:NSTreeController!
@IBOutlet var outlineView:NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
treeController.content = self
}
Run Code Online (Sandbox Code Playgroud)
现在,每次追加一个设备或添加服务只需调用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)
}
Run Code Online (Sandbox Code Playgroud)
这是基础知识,应该让你开始,但文档NSOutlineView和NSTreeController有更多的信息.
编辑
除了上面的内容之外,您还需要将大纲视图绑定到树控制器.
首先确保您的大纲视图处于查看模式.
接下来的表格列绑定到arrangedObjects树控制器上.
最后将文本单元格绑定到相关的键路径.在你的情况下它的名字.objectValue是对单元格中对象的引用.