Programming With Cocoa
by Christopher Roach
An Introduction to RubyCocoa, Part 2
Editor's note -- In Part 1 of this two-part series, Christopher Roach provided some background and helped you get started with RubyCocoa programming. In today's conclusion he gets into the actual code ... and if you're following along, you'll end up with a functioning application.
Creating a Skeleton
The first thing we'll need to do before we can begin coding our application is to add a new Ruby file. We'll need to add a class that inherits from the
NSObject class. To do so, control-click on the "Classes" directory under "Group & Files" in Xcode. Select "Add->New File..." from the pop-up menu, and then select Ruby
NSObject subclass under Ruby in the "New File" dialogue.
Click "Next," and title your new Ruby class
Controller.rb, or whatever you titled your controller class in Interface Builder (IB). Then add the new class to the current project and target and click "Finish." Once you've done this you should have a skeleton for a Ruby class that inherits from the
NSObject class found in the RubyCocoa
OSX module. Next we'll need to add our outlets to the new class.
We add outlets to a Ruby class by calling the
ns_outlets method and passing to it a list of symbols representing the names of the outlets we wish to create. This should look very similar to calling the
attr_writer method that Ruby provides for creating writer methods for instance variables. You could just as easily substitute
attr_writer for the
ns_outlets method and the application would still work without incident. You can also use the alias
ib_outlets to replace the
ns_outlets method as I do in my code.
After we've added all five of our IB outlets to our new Ruby class, we need to add the methods that correspond to the actions we created in IB. We do this by creating a method for each of the actions using the same name as the action we created that takes one parameter for the sender of the message. Once we have added all five actions, we should have a class skeleton similar to the following.
require 'osx/cocoa' class Controller < OSX::NSObject include OSX ib_outlets :archiveFile, :fileTypeView, :fileTableView, :fileType, :mainWindow def addFile(sender) puts "addFile method" end def removeFile(sender) puts "removeFile method" end def browseForArchive(sender) puts "browseForArchive method" end def createArchive(sender) puts "createArchive method" end def extractArchive(sender) puts "extractArchive method" end end
There are two things that you should notice in the code above that are different from what I've already told you to add to your new file. First, I have added a line to each method that prints a string to the console window. This is strictly for testing. If you like, you can go ahead and run your application and try out each of the buttons to make sure that each one works properly. Afterward, make sure you remove each of the
puts statements so that our finished application is not trying to print to the console every time we invoke an action.
Next, you'll notice that I've added a line to the beginning of the class definition that includes the
OSX module found in the RubyCocoa framework. If you'll remember back at the beginning of this tutorial, I stated that every class in the RubyCocoa framework can be found in the
OSX module. What we've done here by including it in our class is to mix-in (Ruby's method for avoiding multiple inheritance) all of these classes, methods, and variables into our class, giving us direct access to everything that the RubyCocoa framework has to offer.
So, you've tried out your new RubyCocoa application and everything seems to be working fine so far, right? And, you've also gone back through the code and scrubbed it by removing all of those unnecessary
puts commands, correct? Great, then you've got everything ready for our final section in which we add all of the rest of the class's internals and get our application fully functioning.
Adding the Guts
We'll start by setting up the methods that will be used to manipulate our
NSTableView object. The two methods we need to implement for this are the
removeFile methods. However, before we can get these methods working properly we'll need to set up an object to act as our data source for the
Our application is simple enough that we will not be creating a separate data structure to act as the data source for our
NSTableView. Instead, our
Controller will be acting as our data source. To do this, we need to implement two methods in our
Controller class as a minimum to allow it to act as a
NSTableView data source. These two methods are the
numberOfRowsInTableView method that returns the number of files in our table and the
tableView_objectValueForTableColumn_row method that returns the value found in the cell currently selected in our table. Below is the implementation for each of these methods.
### # numberOfRowsInTableView # Returns the number of records in the table. # This must be implemented by whatever class # acts as the data source for the NSTableView # class. ### def numberOfRowsInTableView(afileTable) @files.length end ### # tableView_objectValueForTableColumn_row # Returns the value corresponding to the cell # (row and column intersection) the user has # currently selected. This must be # implemented by whatever class acts as the # data source for the NSTableView class. ### def tableView_objectValueForTableColumn_row( afileTable, aTableColumn, rowIndex) @files[rowIndex] end
Both of these methods are pretty simple for our application. The first method simply returns the number of elements in an array called
@files (we'll find out more about this array in a second). The second method normally takes the point of intersection between the column and row currently selected and returns a value representing the chosen cell. However, our application only has a single column, so that makes the column number unnecessary in discovering the value of the chosen cell. All we need to do is return the element found at the index correlating to the currently selected row, which is exactly what we do in the code above. Now we just need to create an instance of the
Array class and assign it our
@files instance variable.
@files array that we use to hold the names of the files to be found in our application's table view object needs to be created before our two new methods are called. The best way to do this is to add an
initialize method (Ruby's class constructor) and create an instance of the
Array class there, just like the code below shows.
### # initialize # This is the Ruby constructor for a class. # We use it to create a new # instance of the Array class to hold records # for our files table. ### def initialize @files = Array.new end
We now have our necessary methods for acting as a data source for the
NSTableView object and we have created the array that will hold the information displayed in that table, but we still haven't implemented the
removeFile methods that will manipulate the table. Lets go ahead and do that now.
First, add the following implementation to our
### # addFile # Displays an instance of the NSOpenPanel and # gets the name and location of one or more # files that the user wishes to add to the # archive. ### def addFile(sender) oPanel = NSOpenPanel.openPanel oPanel.setAllowsMultipleSelection(true) buttonClicked = oPanel.runModal if buttonClicked == NSOKButton files = oPanel.filenames count = files.count for i in 0..count - 1 @files.push( files.objectAtIndex(i)) end @fileTableView.reloadData end end