简体   繁体   中英

Xcode9 Swift4.2 NSOutlineView NSTreeController data

I am trying to create an outlineview in a MacOS app that has mutliple levels that are summaries for a set of data held in SQLite3. I have an outlineview working with a treecontroller with a very simple NSMutuableDictionary based on a model class.

import Cocoa
class Summary: NSObject {
    @objc dynamic var name: String
    @objc dynamic var trades: Int
    @objc dynamic var avgPL: Double
    @objc dynamic var pandl: Double
    @objc dynamic var parent: String
    @objc dynamic var isLeaf: Bool
    @objc dynamic var childCount: Int
    @objc dynamic var children: [Summary] = []
    init(name: String, trades: Int, avgPL: Double, pandl: Double, parent: String, isLeaf: Bool,childCount: Int) {
        self.name = name
        self.trades = trades
        self.avgPL = avgPL
        self.pandl = pandl
        self.parent = parent
        self.isLeaf = isLeaf
        self.childCount = childCount
    }
    @objc func add(child: Summary) {
        children.append(child)
    }
}

My simple example data is:

    let root: [String : Any] = ["name": "Overall","trades":5,"avgPL":200,"pandl":500,"parent":"","isLeaf": false,"childCount": 2 ]
    let dict: NSMutableDictionary = NSMutableDictionary(dictionary: root)
    let l2a = Summary(name: "L2a", trades: 3, avgPL: 100, pandl: 300, parent: "L1",isLeaf: true,childCount: 0)
    let l2b = Summary(name: "L2b", trades: 2, avgPL: 100, pandl: 200, parent: "L1",isLeaf: true,childCount: 0)
    dict.setObject([l2a,l2b], forKey: "children" as NSCopying)

I pass the dictionary to the treeController:

treeController.addObject(dict)

And that works nicely giving me a collapsible outline:

在此处输入图片说明

But I have no idea how to add more levels or children to the children. I want to have up to four levels deep in the outline. I have all the SQL summaries working and I have tried so many variations of populating arrays and trying to create a dictionary with the data to no avail. I have children and childCount and isLeaf set on everything but treecontroller does not like the array complaining that isLeaf is not KVO compliant. My data in an array looks like this (not all of the data but enough to see what I'm doing) The main level and all of the subsequent children are all based on the Summary model class above. Can I simply convert this array to a dictionary? Or, can I make it KVO compliant by adding keys to the model class or something? I have all of the 4 levels in separate arrays I use to build the resultant array if that is useful :

在此处输入图片说明

I should add that I have an NSObject defined as an NSMutableArray and its content tied to the treeController. My treeController is bound to each variable in the model class and at the top level has:

在此处输入图片说明

If I pass the array I have built to the treeController I get the following error:

Failed to set (contentViewController) user defined inspected property on (NSWindow): [<_TtGCs23_ContiguousArrayStorageC11outlinetest7Summary_ 0x604000445160> addObserver:forKeyPath:options:context:] is not supported. Key path: isLeaf

After building out my NSOutlineView without an NSTreeController and getting everything working I still wanted to get back to this and implement the treeController in order to take advantage of the sorting mechanism it provides. And I did find as per my last comment that I did have something wrong in InterfaceBuilder that was causing it to complain about KVO compliance. I had everything wired correctly except for the Content Array binding on the treeController. Here I bound it to my ViewController and added my data array reportSummary to the Model Key Path.

在此处输入图片说明

I also no longer needed to manually add my data array to the treeController using treeController.addObject(reportSummary) . Once this was working I was then able to implement sorting and everything is working well. I should point out two things.

  1. Setup of sorting on the TreeController is slightly different than on an ArrayController tied to a TableView. With the tableview it was sufficient to specify which columns are sortable in the identity inspector in IB. But in the outlineView scenario I also needed to setup bindings in IB to the treeController and change the Controller Key from arrangedObjects to sortDescriptors .

  2. While testing my tree controlled outlineview I ran into a problem when I double-clicked on a summary row. I had implemented Double Action on the outlineView in IB in order to control the expanding and collapsing of summary sections. Note that I read about doing this in a thread here and someone mentioned that you would need to maintain multiple arrays and track indexes because once a row is collapsed or expanded that changes the row number of all the subsequent rows. But I figured out that the solution is simply to iterate through rows in reverse order and expand or collapse them working back up the tree starting from outlineView.numberOfRows-1 . This works well and along with Double Action (clicking) to expand and collapse I also added an NSSlider which tracks to the expansion level and lets me collapse all the lowest levels moving back up the tree instead of clicking all of the little arrows on each row. This broke when I implemented the treeController. I received an error

Could not cast value of type 'NSKVONotifying_NSTreeControllerTreeNode'

This line of code was the problem

let summary = reportOutline.item(atRow: x) as! Summary

I had to change this to

let node = reportOutline.item(atRow: x) as! NSTreeNode
let summary = node.representedObject as! Summary

And that is it. Working beautifully now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM