Swift - Why is argument item in NSOutlineView always nil? - swift

I am attempting to implement a simple sourceView, however, I am only managing to set the stringValue of one textField.
First, this is the structure of my program:
class MainWindowController: NSWindowController, NSOutlineViewDataSource, NSOutlineViewDelegate
{
#IBOutlet weak var sourceView: NSOutlineView!
override func windowDidLoad()
{
sourceView.setDataSource(self)
sourceView.setDelegate(self)
}
}
Then, I have:
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int
{...}
And finally,:
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject
{...}
My issue is that in both of these functions, the value of item is nil. Is this what is supposed to happen?

It doesn't appear you're providing the object value. Unless you're using an NSTreeController to populate your NSOutlineView, you must also implement the data source function:
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
// return your object value
}

Related

NSOutlineView, how to get the selected cell

I want to get the selected cell from the NSOutlineView control. I found a solution here: How can I get the selected cell from a NSOutlineView?. It says
Use the delegate. willDisplayCell: is called when a cell changes its selection state.
However when I test it, I found that my willDisplayCell: is not be called.
Here's my code, it can be run normally, but the willDisplayCell: method has never been called. Where did I make a mistake? Thanks.
class TreeNode: NSObject{
var name: String = ""
private(set) var isLeaf: Bool = false
var children: [TreeNode]?
init(name: String, isLeaf: Bool){
self.name = name
self.isLeaf = isLeaf
if !isLeaf{
children = [TreeNode]()
}
}
}
class ViewController: NSViewController {
#IBOutlet weak var sourceList: NSOutlineView!
private var data = TreeNode(name: "Root", isLeaf: false)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
for i in 0..<10{
let node = TreeNode(name: "name \(i)", isLeaf: i % 2 == 0)
data.children?.append(node)
}
}
}
extension ViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let item = item as? TreeNode, !item.isLeaf {
return item.children!.count
}
return 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return !((item as? TreeNode)?.isLeaf ?? false)
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let item = item as? TreeNode {
if item.isLeaf{
return item
}else{
return item.children![index]
}
}
return data
}
}
extension ViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, item: Any) {
print("called")
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let cell: NSTableCellView?
if let item = item as? TreeNode{
cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.name
}else{
cell = nil
}
return cell
}
}
I've solved this problem myself. The following code is how to get the selected cell:
func getSelectedCell() -> NSTableCellView? {
if let view = self.sourceList.rowView(atRow: self.sourceList.selectedRow, makeIfNecessary: false) {
return view.view(atColumn: self.sourceList.selectedColumn) as? NSTableCellView
}
return nil
}
Now I can access the NSTextField control by the code getSelectedCell()?.textField.

NSOutlineView drag line stuck + blue border

I'd like a correct behaviour from the blue drag bar, and no blue rect when dragging.
Do you know where is my mistake ?
(as you can see, the blue bar is stuck in top, like in this topic :
Little circle-line bar stuck at top of NSOutlineView when rearranging using drag and drop)
import Cocoa
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate, NSPasteboardItemDataProvider {
#IBOutlet weak var outlineView: NSOutlineView!
let REORDER_PASTEBOARD_TYPE = "com.test.calques.item"
override func viewDidLoad() {
super.viewDidLoad()
//Register for the dropped object types we can accept.
outlineView.register(forDraggedTypes: [REORDER_PASTEBOARD_TYPE])
//Disable dragging items from our view to other applications.
outlineView.setDraggingSourceOperationMask(NSDragOperation(), forLocal: false)
//Enable dragging items within and into our view.
outlineView.setDraggingSourceOperationMask(NSDragOperation.every, forLocal: true)
outlineView.delegate = self;
outlineView.dataSource = self;
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
var items: [(String, NSColor)] = [
("Item 1", NSColor.black),
("Item 2", NSColor.red),
("Item 3", NSColor.red),
("Item 4", NSColor.red),
("Item 5", NSColor.red),
("Item 6", NSColor.red)];
//NSOutlineViewDataSource
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return items.count;
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
return items[index];
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let image: NSImage = NSImage(size: NSSize(width: 17, height: 17));
let calquesItem: (String, NSColor) = item as! (String, NSColor);
let path = NSBezierPath(ovalIn: CGRect(x: 2, y: 2, width: 17 - 4, height: 17 - 4));
image.lockFocus();
calquesItem.1.setFill();
path.fill();
image.unlockFocus();
let cell = outlineView.make(withIdentifier: "DataCell", owner: nil) as! NSTableCellView;
cell.textField!.stringValue = calquesItem.0;
cell.imageView!.image = image;
return cell;
}
//Drag - NSOutlineViewDataSource
var fromIndex: Int? = nil;
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
let pastBoardItem: NSPasteboardItem = NSPasteboardItem();
pastBoardItem.setDataProvider(self, forTypes: [REORDER_PASTEBOARD_TYPE]);
return pastBoardItem;
}
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
Swift.print("willBeginAt")
let item = draggedItems[0] as! (String, NSColor);
fromIndex = items.index(where: { (_item: (String, NSColor)) -> Bool in
return _item.0 == item.0
});
session.draggingPasteboard.setData(Data(), forType: REORDER_PASTEBOARD_TYPE)
}
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
Swift.print("acceptDrop")
if(fromIndex! != index && index != -1) {
let toIndex: Int = fromIndex! < index ? index - 1 : index;
outlineView.moveItem(at: fromIndex!, inParent: nil, to: toIndex, inParent: nil);
items.insert(items.remove(at: fromIndex!), at: toIndex);
return true;
}
return false;
}
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
if(item == nil) {
return NSDragOperation.generic;
}
return [];
}
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
Swift.print("Drag session ended")
fromIndex = nil;
}
//NSPasteboardItemDataProvider
func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String)
{
item.setString("Outline Pasteboard Item", forType: type)
}
}
From https://developer.apple.com/reference/appkit/nsoutlineview :
Each item in the outline view must be unique. In order for the
collapsed state to remain consistent between reloads the item's
pointer must remain the same and the item must maintain isEqual(_:)
sameness.
I was using Tuples (String, NSColor). And Tuples don't conform to the hashable protocol !
After switching from Tuples items to MyClass items, everything works fine !
(correct behaviour from the blue drag bar, and no blue rect when dragging)
class MyClass {
var name: String!
var color: NSColor!
init(_ _name: String, _ _color: NSColor) {
name = _name
color = _color
}
}

Simple NSOutlineView Retain Crash

I've got a simple NSOutlineView setup in my OSX Swift project feeding from a basic array but its causing an EXC_BAD_ACCESS crash.
Having enabled zombies, it crashes with the following error:
[NSMutableIndexSet retain]: message sent to deallocated instance
Heeeelp! Here is my code:
class SidebarViewController: NSViewController, NSOutlineViewDataSource {
//MARK: Vars
#IBOutlet var sidebar : NSOutlineView?
var data : [String] = ["Assemblies", "Parts", "Customers"]
//MARK: Init
override func viewDidLoad()
{
super.viewDidLoad()
// Do view setup here.
}
//MARK: NSOutlineView Delegate / Datasource
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int
{
return data.count
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool
{
return false
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject
{
return data[index]
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject?
{
return item
}
}
Turns out that objectValueForTableColumn method require an obj_c object returned. So returning String in my array doesn't work. I changed this to NSString and it now works.

NSOutlineView leaks memory. What to do?

When a minimal OSX application that has nothing else but a plain (unconfigured) NSOutlineView in its storyboard, it will heavily leak memory.
View controller implementation
The view controller is implemented as following:
import Cocoa
class Section {
let title: String
let items: [String]
init(title: String, items: [String]) {
self.title = title
self.items = items
}
}
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
//MARK: NSOutlineViewDataSource
let items = [
Section(title: "Alpha", items: ["Anton", "Anita"]),
Section(title: "Beta", items: ["Bernd", "Barbara"]),
Section(title: "Gamma", items: ["Gustav", "Gesine"])]
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
switch item {
case nil: return items.count
case is Section: return (item as! Section).items.count
default: return 0
}
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
return item == nil
? items[index]
: (item as! Section).items[index]
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
return item is Section
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
return item
}
//MARK: NSOutlineViewDelegate
func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
let view = outlineView.makeViewWithIdentifier(item is Section ? "HeaderCell" : "DataCell", owner: self) as! NSTableCellView
view.textField?.stringValue = item is Section
? (item as! Section).title
: item as! String
return view
}
}
The Xcode project can be cloned from github
Xcode showing a constantly increasing memory consumption
When the app is started and tree items are selected by clicking them, the memory consumption increases tremendously.
Xcode's debug navigator shows an increasing memory consumption over a short time. Memory increases only when you navigate through the NSOutlineView nodes.
Instruments not showing something suspicious (or does it?)
Instruments however is showing a constant memory usage, that is neither increasing nor decreasing:
What can I conclude from Instrument's report? Is it not detecting leaks because it's NSOutlineView itself whose leaking (and those can't be detected?) or is there simply no leak at the end?

NSOutlineView EXC_BAD_ACCESS crash on expand

I am trying to use an NSOutlineView in my project, using Swift.
I have done this a few times already using Objective-C without any problem, but for some reason now my app keeps crashing with EXC_BAD_ACCESS, mostly when trying to expand a cell.
I have created a new test project with only an outline view and the 4 data source methods, but the crashes happen, there, too.
Here is the minimal implementation:
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
return 3
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
return "Test"
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
return true
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
return nil
}
Any idea of what I am doing wrong?
I have uploaded the test project here. Please review it. https://drive.google.com/file/d/0BzEhecUbyNeFS3JGN1V0SlJ0dWM/view
There are a few things I had to fix to make your sample work as expected:
There's a crash related to "Test" being a local string and released before the outline view tries to retain it. This is fixed by having the items owned by the view controller.
After that, I encountered a crash due to infinite recursion. That was fixed by having a data model instead of telling the outline view that every item had 3 children no matter what level the item was at.
I also changed the items to be instances subclassed from NSObject rather than String because I remember reading that that was necessary. (I currently can't find the reference.)