RubyCocoa MultipleNibTabView sample
November 7, 2007
The example from the Apple Developer Tools often show interesting techniques that can be successfully used in real projects. One of those is the MultipleNibTabView example (available under /Developer/Examples/InterfaceBuilder/MultipleNibTabView) , showing how you how to build user interfaces in a more modular way. This is the official project description (ReadMe.rtf):
This example application demonstrates how to use views housed in separate nibs. The main window has an NSTabView and an NSTextField. The window controller creates a SubViewController which loads in a nib and returns the nib’s view. The window controller inserts those views into each NSTabViewItem. The SubViewController sends a message to the Window Controller, requesting a pointer to the NSTextField. The widgets in each view applies gets that pointer by sending a message to the SubViewController and then applies changes to the text and color of the NSTextField.
Here it goes its RubyCocoa implementation. Simply create a new RubyCocoa application in XCode, copy over the English.lproj folder and add the source files defined below.
#
# WindowController.rb
#
require 'osx/cocoa'
class WindowController < OSX::NSWindowController
ib_outlet :displayTextHere
ib_outlet :multipleNibTabView
attr_accessor :enterTextNibView
attr_accessor :setColorNibView
def init
@enterTextNibView = nil
@setColorNibView = nil
super_init
end
def awakeFromNib
# Ensure that the first tab is selected.
@multipleNibTabView.selectTabViewItemWithIdentifier('1')
end
def viewFromNibWithName(nibName)
# Creates an instance of SubViewController which loads the specified nib.
subViewController = SubViewController.alloc.initWithNibName_andOwner(nibName, self)
subViewController.view
end
def tabView_didSelectTabViewItem(tabView, tabViewItem)
nibName = nil
# The NSTabView will manage the views being displayed but without the NSTabView, you need to use removeSubview: which releases the view and you need to retain it if you want to use it again later.
# Based on the tab selected, we load the appropriate nib and set the tabViewItem's view to the view fromt he nib
if (tabViewItem.identifier.isEqualToString('1'))
if !(@enterTextNibView)
@enterTextNibView = self.viewFromNibWithName('EnterText')
end
if (@enterTextNibView)
tabViewItem.setView(@enterTextNibView)
end
end
if (tabViewItem.identifier.isEqualToString('2'))
if !(@setColorNibView)
@setColorNibView = self.viewFromNibWithName('SetColor')
end
if (@setColorNibView)
tabViewItem.setView(@setColorNibView)
end
end
end
def textField
# This method returns a pointer to the NSTextField on the main window.
@displayTextHere
end
end
#
# SubviewController.rb
#
require 'osx/cocoa'
class SubViewController < OSX::NSObject
ib_outlet :view
attr_accessor
wner
def init
super_init
end
def initWithNibName_andOwner(nibName, owner)
@owner = owner
OSX::NSBundle.loadNibNamed_owner(nibName, self)
init
end
def view
OSX::NSLog('Getting View %@\n', description)
@view
end
def ownerTextField
@owner.textField
end
end
#
# TextFormattinView.rb
#
require 'osx/cocoa'
class TextFormattingView < OSX::NSView
ib_outlet :backgroundColorWell
ib_outlet
wner
ib_outlet :textColorWell
# This method is called when the nib is finished loading. It makes sure the color wells are set to the colors of the text color and the background color of the NSTextField from the main window.
def awakeFromNib
@textColorWell.setColor(@owner.ownerTextField.textColor)
@backgroundColorWell.setColor(@owner.ownerTextField.backgroundColor)
end
# This method asks the SubView Controller for a pointer to the NSTextField on the main window and then sets its background color.
def setBackgroundColor(sender)
@owner.ownerTextField.setBackgroundColor(sender.color)
end
# This method asks the SubView Controller for a pointer to the NSTextField on the main window and then sets its text color.
def setTextColor(sender)
@owner.ownerTextField.setTextColor(sender.color)
end
end
#
# TypeTextHere.rb
#
require 'osx/cocoa'
class TypeTextHere < OSX::NSView
ib_outlet
wner
ib_outlet :text
def sendText(sender)
@owner.ownerTextField.setStringValue(sender.stringValue)
end
end
Low-level Core Data Tutorial
August 15, 2007
As you all know, simple Core Data applications can be built without much coding thanks to XCode Data Modeler and Interface Builder and, for basic things, you seldom need to deal with Core Data API itself. Nevertheless, a Core Data API does exist that allows you to programmatically define your data model, and in some occasions, it could be appropriate to define a data model programmatically.
A basic introduction to the Core Data API is given in the “Low-Level Core Data Tutorial” from Apple, which guides you through the steps necessary to build a simple command line app that stores a log of all its executions and displays then the log content.
The Low-level Core Data tutorial was partially ported some time ago to RubyCocoa by Ernest Prabhakar. In particular, how to execute a fetch request was not covered, so I decided to take Ernest’s sample a little bit further by implementing that part, left out in the first place. As I was at it, I also polished a little bit the original code, taking advantage of various RubyCocoa enhancements since version 0.4.2, current at the time the original post was written.
Core Data Command Line Client
# Port to Ruby/RubyCocoa of the Low-level CoreData Tutorial
# from Apple.
# Apple Inc. © 2005, 2006 Apple Computer, Inc.
#
# The RUbyCocoa version is released under the MIT License
require 'osx/cocoa'
OSX.require_framework 'CoreData'
module CoreDataCLI
#-- constants
STORE_TYPE = OSX::NSXMLStoreType
STORE_FILENAME = "CDCLI.xml"
#-- module variables
@@mom = nil
@@moc = nil
#-- functions
def self.managedObjectModel
if @@mom
return @@mom
end
@@mom = OSX::NSManagedObjectModel.alloc.init
runEntity = OSX::NSEntityDescription.alloc.init
runEntity.setName('Run')
runEntity.setManagedObjectClassName('Run')
@@mom.setEntities(OSX::NSArray.arrayWithObject(runEntity))
#-- date attribute
dateAttribute = OSX::NSAttributeDescription.alloc.init
dateAttribute.setName('date')
dateAttribute.setAttributeType(OSX::NSDateAttributeType)
dateAttribute.setOptional(false)
#-- processID attribute
idAttribute = OSX::NSAttributeDescription.alloc.init
idAttribute.setName('processID')
idAttribute.setAttributeType(OSX::NSInteger32AttributeType)
idAttribute.setOptional(false)
idAttribute.setDefaultValue(OSX::NSNumber.numberWithInt(-1))
#-- Validation Predicate and Warning
lhs = OSX::NSExpression.expressionForEvaluatedObject
rhs = OSX::NSExpression.expressionForConstantValue(OSX::NSNumber.numberWithInt(0))
validationPredicate = OSX::NSComparisonPredicate.objc_send(
:predicateWithLeftExpression, lhs,
:rightExpression, rhs,
:modifier, OSX::NSDirectPredicateModifier,
:type, OSX::NSGreaterThanOrEqualToComparison,
ptions, nil)
validationWarning = OSX::NSLocalizedString("Process ID must not be less than 0.",
"Process ID must not be less than 0.")
idAttribute.objc_send(
:setValidationPredicates, OSX::NSArray.arrayWithObject(validationPredicate),
:withValidationWarnings, OSX::NSArray.arrayWithObject(validationWarning))
runEntity.setProperties(OSX::NSArray.arrayWithObjects(dateAttribute, idAttribute, nil))
return @@mom
end
LOG_DIR = "CDCLI"
def self.applicationLogDirectory
ald = nil
if (ald != nil)
return ald
end
paths = OSX::NSSearchPathForDirectoriesInDomains(
OSX::NSLibraryDirectory,
OSX::NSUserDomainMask,
true)
if (paths.count == 1)
ald = paths.to_a[0].to_s + "/Logs/" + LOG_DIR
fileManager = OSX::NSFileManager.defaultManager
isDirectory = "NO"
if fileManager.fileExistsAtPath_isDirectory(ald, isDirectory)
return ald
end
if fileManager.createDirectoryAtPath(ald, :attributes, nil)
return ald
end
ald = nil
end
end
def self.managedObjectContext
if (@@moc)
return @@moc
end
@@moc = OSX::NSManagedObjectContext.alloc.init
coordinator = OSX::NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(managedObjectModel)
log_file = applicationLogDirectory() + "/" + STORE_FILENAME
url = OSX::NSURL.fileURLWithPath(log_file)
print "url=", url, "\n"
# newStore, error = coordinator.addPersistentStoreWithType_configuration_URL_options_error(STORE_TYPE, nil, url, nil, nil)
newStore, error = coordinator.objc_send(:addPersistentStoreWithType, STORE_TYPE,
:configuration, nil,
:URL, url,
ptions, nil,
:error, error)
if (newStore == nil)
OSX::NSLog("Store configuration Failure\n%@", error.localizedDescription)
end
@@moc.setPersistentStoreCoordinator(coordinator)
@@moc
end
class Run 1000) AND (processID < 8580)")
request.setPredicate(predicate)
result, err = moc.executeFetchRequest_error(request, err)
if (result == 0 || err)
OSX::NSLog("Error while fetching\n%@", err.localizedDescription)
exit -3
end
enumerator = result.objectEnumerator
while (run = enumerator.nextObject) != nil
if (run)
print "On ", run.date, " as process ID ", run.processID, "\n"
end
end
Remarks
If you copy/paste the code above in a text file, let’s call it coredata_cli.rb for reference, each time you execute it in a shell terminal, it will log the execution date/time and process ID and then list the whole log content. You can play around with the predicateWithFormat argument so to filter the log by process ID or date, if you like. This code has been tested with RubyCocoa 0.11.0.
In comparison to the ObjectiveC implementation, look at how much cleaner is the Ruby code given the absence of memory management (retain/release) and the power of the kvc_wrapper directive provided by the RubyCocoa bridge.
PS: The cdcli.rb file linked from Ernest Prabhakar’s blog is not available anymore at that location. I could retrieve it thanks to the great web WayBack Machine accessible here and you can found the ruby file here.