NSOutlineView crashes with RubyCocoa all the time | 19. Februar 2009 um 23:40 Uhr / Programming

Eingestellt am 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.


Kommentare

  Dein Kommentar 

Wie benutze ich BBcode?