Использование NSTreeController с NSOutlineView
Я пытаюсь (безуспешно) построить управляемый 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. Я установил Свойство Object Controller TreeController на Mode: Class и Class: Device, и не устанавливая свойства Children, Count или Leaf, которые я получаю (как и ожидалось):
2017-01-04 17: 20: 19.337129 OutlineTest [12550: 1536405] Предупреждение: [класс объекта: устройство] childrenKeyPath не может быть равен нулю. Чтобы устранить это сообщение журнала, установите атрибут childrenKeyPath в Интерфейсном Разработчике
Если я тогда установлю свойство Children в значение 'children', все будет очень плохо:
2017-01-04 17: 23: 11.150627 OutlineTest [12695: 1548039] [General] [addObserver: forKeyPath: options: context:] не поддерживается. Ключевой путь: дети
Все, что я пытаюсь сделать, это настроить NSOutlineView для получения ввода от NSTreeController, чтобы при добавлении нового "Device" в массив devices[] он отображался в Outline View.
Если бы кто-нибудь мог указать мне правильное направление здесь, я был бы очень благодарен.
Огромная благодарность Уоррену за чрезвычайно полезную работу. У меня это (в основном) работает. Пара вещей, которые мне также нужно было сделать, в дополнение к предложениям Уоррена:
Установите хранилище данных для контроллера дерева
Привязать OutlineView к TreeController
Привязать столбец к 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()
Без этого ничего. У меня все еще нет корректного обновления данных (см. Комментарии ниже ответа Уоррена), но я почти на месте.
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
возражаете ответить на 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 []
}
}
Объект 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
}
}
И это ужасная вещь с точки зрения 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
& NSTreeController
есть намного больше информации.
РЕДАКТИРОВАТЬ
В дополнение к вышеперечисленному материалу вам необходимо привязать ваш контурный вид к контроллеру дерева.
Сначала убедитесь, что ваш контурный вид находится в режиме просмотра.
Затем привяжите столбец таблицы к композиция Objects на контроллере дерева.
Последнее связать текстовую ячейку с соответствующим путем ключа. В твоем случае это имя. objectValue - это ссылка на ваш объект в ячейке.