NSOutlineView crashes with RubyCocoa all the time | 19. Februar 2009 um 23:40 Uhr / Programming
Finally after 18 hours of trying and failing I got it working!
I'm writing a little RubyCocoa Twitter-client called Twittia and wanted to use NSOutlineView as something like a navigation-bar. So I looked on all the tutorials; of course I didn't find any which shows how to use it with Ruby.
First I tried to write a simple NSOutlineViewDataSource delegate. The items showed up and I was really happy and thought: "That was easy". But then I tried to resize the window or open a group and my app crashed, each time and without any error in the log which could help me.
So I looked deeper into the Internet and copied this Objective-C code as exactly as I could into Ruby: http://www.cocoadev.com/index.pl?NSOutlineViewDataSource. But it kept on crashing.
I often asked the irc.freenode.net#ruby-osx IRC-chanel for help but nobody answered, until half an hour ago. Thanks to octopod and lrz I finally fixed my NSOutlineView, here is our conversation:
octopod: Jeena: you need to make sure you're returning the *exact* same object each time
Jeena: thats why I just copied the obj-c implementation, because I thought it will be implemented the right way but no luck here
lrz: yes, you should make sure you return pure cocoa objects from the data source implementation
lrz: for instance, an NSString and not a ruby string
lrz: otherwise the bridge will create copies and it will confuse the outline view
lrz: i would suggest to make sure you return unique cocoa objects
octopod: Jeena: iirc, the item that you return should be a NSObject subclass to make it not crash
Jeena: so I do class MyString < OSX::NSObject and use them instead?
octopod: so rather than use the ruby hash, try an NSDictionary
Jeena: the only method that returns a value is outlineView_objectValueForTableColumn_byItem isn't it?
octopod: I think the problem is that rubycocoa makes a proxy behind the scenes for what's returned from outlineView_child_ofItem if it's not a NSObject subclass
Jeena: hm ok
octopod: and it makes a new proxy each time, which breaks NSOutlineView
lrz: yep, thats the thing
lrz: you need to make sure you always return pure cocoa and unique objects
Jeena: ok I'll try to use NSDictionary
lrz: the dictionary might not be relevant here, but the objects inside the dictionary
Jeena: I think the outlineView_child_ofItem returns a dictionary isn't it?
lrz: yep
lrz: and outlineView_objectValueForTableColumn_byItem returns an object inside the dictionary
Jeena: Ok I'll use NSDictionary and NSString instead
Jeena: very very cool!! thanks a lot! it does work now. Honestly, I tried to figure it out for the last three days, each day about 6 hours
octopod: cool, I think having NSOutlineView crash like that is a stage every rubycocoa developer goes through!
octopod: the 'hidden' rubycocoa proxy is the key, it's not something you'd think about to start with, or even know about really
So here is my implementation of NSOutlineView in RubyCocoa:
require 'osx/cocoa'
class WeakReference < OSX::NSObject
attr_accessor :parent
def self.weakReferenceWithParent parent
weakRef = WeakReference.alloc.init
weakRef.setParent parent
weakRef
end
def setParent parent
@parent = parent
end
def parent
@parent
end
end
class Sidebar < OSX::NSObject
ib_outlet :outlineView
def initialize
@dataStore = OSX::NSMutableArray.array
@groups = {
:places => { :name => "PLACES", :index => 0 },
:searches => { :name => "SEARCHES", :index => 1 }
}
@defaults = OSX::NSUserDefaults.standardUserDefaults
@groups.each do |key, group|
@dataStore.addObject(groupWithTitle(group[:name]))
end
add_places
replace_searchwords
end
def groupWithTitle title
group = OSX::NSMutableDictionary.dictionary
group.setObject_forKey(OSX::NSMutableArray.array, "CHILDREN")
group.setObject_forKey(title, "TITLE") unless title.nil?
group
end
def removeChildren group
group.objectForKey("CHILDREN").removeAllObjects
end
def newItemForGroup_title group, title
item = OSX::NSMutableDictionary.dictionary
children = group.objectForKey "CHILDREN"
children.addObject item
item.setObject_forKey(title, "TITLE") unless title.nil?
item.setObject_forKey(WeakReference.weakReferenceWithParent(group), "PARENT") unless group.nil?
item
end
def awakeFromNib
@outlineView.setSelectionHighlightStyle OSX::NSTableViewSelectionHighlightStyleSourceList
@outlineView.reloadData
end
def add_places
group = @dataStore.objectAtIndex @groups[:places][:index]
newItemForGroup_title group, "Friends timeline"
newItemForGroup_title group, "Replies"
newItemForGroup_title group, "Direct messages"
newItemForGroup_title group, "Favourites"
end
def replace_searchwords
group = @dataStore.objectAtIndex @groups[:searches][:index]
removeChildren group
@defaults.arrayForKey("searchwords").to_a.each do |word|
newItemForGroup_title group, word
end
@outlineView.reloadData unless @outlineView.nil?
end
def outlineView_isItemExpandable ov, item
if item.nil?
children = @dataStore
else
children = item.objectForKey "CHILDREN"
end
not (children.nil? or children.count < 1)
end
def outlineView_numberOfChildrenOfItem ov, item
if item.nil?
children = @dataStore
else
children = item.objectForKey "CHILDREN"
end
children.count
end
def outlineView_child_ofItem ov, index, item
if item.nil?
children = @dataStore;
else
children = item.objectForKey "CHILDREN"
end
if children.nil? or children.count <= index
nil
else
children.objectAtIndex index
end
end
def outlineView_objectValueForTableColumn_byItem ov, tableColumn, item
item.objectForKey "TITLE"
end
def outlineView_shouldSelectItem ov, item
not @groups.any? { |key, hash| hash.has_value? item.objectForKey("TITLE") }
end
def outlineView_willDisplayCell_forTableColumn_item ov, cell, tableColumn, item
# do something here with the cell if you want to
end
end
The code is public domain of course.




abonnieren.