3. Automatically track changes with bindings

Cocoa Bindings is Apple’s name for a software technology for automatically tracking changes to object properties. Central to this is a set of controllers that you can use to automatically manage the user interface elements associated with model objects. You write your model, design your view in Interface Builder, then hook them both to one or more of Apple’s controller objects to keep everything synchronized.

Cocoa Bindings were notoriously hard to learn, but it is easy to use them from Ruby. I think that some of Apple’s problems came from the wrong tool to teach about bindings. Perhaps you’ve heard of the ‘Golden Hammeranti-pattern. Apple’s is the graphical interface. Apple told everyone to use Interface Builder to configure their controller objects, and the only way to do that was by clicking from screen to screen in Interface Builder. After you’ve done it a while, you might eventually get used to it, but you can easily get lost, especially in tutorials that consist of pages and pages of screen shots.

But at the time, Apple really didn’t have a choice. The alternative would have been to make all the connections by writing code in Objective-C, and that would have required many pages of tedious and verbose code. But Ruby is a lot more expressive than Objective-C.

Here is an example online that created bindings in Ruby. You’ll find it on the examples page. The bindings example is called MailDemo. It’s a Ruby port of a Cocoa bindings tutorial by Scott Stevenson. In it, I’ve designed the user interface in Interface Builder, but instead of hooking up the controllers in IB, I exposed the user interface elements to my Ruby code and made all the connections there. On one page, you can see every connection used by this example.

Bindings example [ruby]
  def make_bindings
    @controllerAlias = with(ObjC::NSObjectController.alloc.init) {|c|
      c.set(:content => self)
    }
    @mailboxController = with(ObjC::NSArrayController.alloc.init) {|c|
      c.bind(:attribute => :contentArray, :object => @controllerAlias, 
        :keyPath => "selection.mailboxes")
      c.set(:objectClass => Mailbox)
      @addMailboxButton.set(:target => c, :action => "add:")
      @deleteMailboxButton.set(:target => c, :action => "remove:")
      @mailboxTable[:title].bind(:attribute => :value, :object => c, 
        :keyPath => "arrangedObjects.properties.title")
      options = {:NSDisplayPattern => "%{value1}@ Mailboxes"}
      @mailboxStatusLine.bind(:attribute => :displayPatternValue1, 
        :object => c, :keyPath => "arrangedObjects.@count", 
        :options => options.to_nsdictionary)
    }
    @emailController = with(ObjC::NSArrayController.alloc.init) {|c|
      c.bind(:attribute => :contentArray, :object => @mailboxController, 
        :keyPath => "selection.emails")
      c.set(:objectClass => Email)
      @addEmailButton.set(:target => c, :action => "add:")
      @deleteEmailButton.set(:target => c, :action => "remove:")
      with(@emailTable) {|t|
        t[:address].bind(:attribute => :value, :object => c, 
          :keyPath => "arrangedObjects.properties.address")
        t[:subject].bind(:attribute => :value, :object => c, 
          :keyPath => "arrangedObjects.properties.subject")
        t[:date].bind(:attribute => :value, :object => c, 
          :keyPath => "arrangedObjects.properties.date")
      }
      options = {:NSDisplayPattern => "%{value1}@ Emails"}
      @emailStatusLine.bind(:attribute => :displayPatternValue1, 
        :object => c, :keyPath => "arrangedObjects.@count", 
        :options => options.to_nsdictionary)
      @previewPane.bind(:attribute => :data, :object => c, 
        :keyPath => "selection.properties.body")
    }
  end

What’s important here isn’t the syntax, it’s that every connection to our models and views is here in one place. There’s no need to tediously click through interface builder screens to check your connections.

But if you still want to make your connections in Interface Builder, you can still use Bindings with Ruby objects. In RubyObjC, you just have to be sure they are derived from ObjC::NSObject. See the online example for details.

Did you find an error? Is something missing? Post your comment or suggestion below!

Comments (1) post
  1. London Website Development Tue Dec 08 04:22:54 PST 2009

    Nice post,

    Keep up the good work

    Thanks for bringing this up