Использование 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.

Если бы кто-нибудь мог указать мне правильное направление здесь, я был бы очень благодарен.

Огромная благодарность Уоррену за чрезвычайно полезную работу. У меня это (в основном) работает. Пара вещей, которые мне также нужно было сделать, в дополнение к предложениям Уоррена:

Установите хранилище данных для TreeController Установите хранилище данных для контроллера дерева

Привязать к TreeView Привязать OutlineView к TreeController

Привязать столбец к 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 - это ссылка на ваш объект в ячейке.

привязать данные к ячейке

Другие вопросы по тегам