NSOutlineView crash when isGroupItem delegate method is used with Swift
Asked Answered
B

3

7

I want to deploy Source List using NSOutlineView in a Swift project.

The view controller below works well when the isGroupItem delegate method is not invoked. However, many __NSMallocBlock__ items will be returned when the isGroupItem method is used. Which I have no idea where these items come from. The items I provided are only strings.

class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {

let topLevel = ["1", "2"]
let secLevel = ["1": ["1.1", "1.2"], "2": ["2.1", "2.2"]]

func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
    if let str = item as? String {
        let arr = secLevel[str]! as [String]
        return arr.count
    } else {
        return topLevel.count
    }
}

func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
    return outlineView.parentForItem(item) == nil
}

func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
    var output: String!
    if let str = item as? String {
        output = secLevel[str]![index]
    } else {
        output = topLevel[index]
    }
    return NSString(string: output)
}

func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
    return item
}

func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool {
    return (outlineView.parentForItem(item) == nil)
}

func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
    return outlineView.makeViewWithIdentifier("HeaderCell", owner: self) as NSTextField
}
}

The sample project can be downloaded here

Bejewel answered 11/12, 2014 at 13:38 Comment(2)
Smells like a bug. If you return true it crashes. If you return false it shows the parent line as malloc...Quicktempered
This arguably is a bug; it should "just work", but doesn't. Please see my response (coming soon).Countersubject
B
5

This question has been answered by Ken Thomases in apple developer forum. Here extracted what he said:

The items you provide to the outline view must be persistent. Also, you have to return the same item each time for a given parent and index. You can't return objects that were created ad hoc, like you're doing in -outlineView:child:ofItem: where you call the NSString convenience constructor.

It works fine after persisting the datasource objects as follow:

let topLevel = [NSString(string: "1"), NSString(string: "2")]
let secLevel = ["1": [NSString(string: "1.1"), NSString(string: "1.2")], "2": [NSString(string: "2.1"), NSString(string: "2.2")]]

then return the stored NSString in the outlineView:child:ofItem: datasource method.

Bejewel answered 12/12, 2014 at 14:45 Comment(2)
I fiddled around with this a bit more. See here: #24829053Quicktempered
I think it is important to understand why this works. Please see my response.Countersubject
C
8

If you check out the NSOutlineView documentation you will see that it stores only pointers; it doesn't retain the objects returned from the child:ofItem: delegate method. So, when you do this line:

return NSString(string: output)

You are returning a new NSString instance that is quickly released (since the outline view does not retain it). After that point, anytime you ask questions about the items you will get a crash, because the NSString has been freed.

The solution is simple: store the NSStrings in an array and return those same instances each time.

corbin

Countersubject answered 13/1, 2015 at 15:25 Comment(0)
B
5

This question has been answered by Ken Thomases in apple developer forum. Here extracted what he said:

The items you provide to the outline view must be persistent. Also, you have to return the same item each time for a given parent and index. You can't return objects that were created ad hoc, like you're doing in -outlineView:child:ofItem: where you call the NSString convenience constructor.

It works fine after persisting the datasource objects as follow:

let topLevel = [NSString(string: "1"), NSString(string: "2")]
let secLevel = ["1": [NSString(string: "1.1"), NSString(string: "1.2")], "2": [NSString(string: "2.1"), NSString(string: "2.2")]]

then return the stored NSString in the outlineView:child:ofItem: datasource method.

Bejewel answered 12/12, 2014 at 14:45 Comment(2)
I fiddled around with this a bit more. See here: #24829053Quicktempered
I think it is important to understand why this works. Please see my response.Countersubject
L
0

It's because NSOutlineView works with objects inherited from NSObject, and Swift string is incompatible type.

Lob answered 13/4, 2017 at 21:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.