Pain in the Nib

May 28th, 2006

Anybody who’s spent time with Apple’s Interface Builder (IB) application knows that it’s an amazingly intuitive, powerful tool for designing user interfaces. It’s also a major pain in the ass.

While Xcode has improved by leaps and bounds over the past few years, IB has struggled to keep up. For the most part, it seems that changes to IB have been the bare minimum that would placate the masses (and management). IB had to support Carbon, so they tacked on Carbon support. IB had to support Cocoa Bindings, so they added a new inspector pane. There have been some really cool improvements such as the “Compatibility Checking,” but the number of frustrating shortcomings leave me always hoping that a major renovation will soon come.

My problems with IB wouldn’t be nearly so bad if there were viable workarounds. Many of my criticisms stem from its inability to effectively edit multiple items at once. Many times I find myself repeatedly setting the same properties on an item, or typing highly similarly but mildly deviating bindings into a key value field. The problems I face in IB all the time are the kinds of problems I would have long ago automated, if only there was an easy way.

Nibtool is the command-line counterpart to IB, and new programmers on the Mac often assume that it offers exactly the kind of flexibility I have been wishing for. Here we have a tool that dumps a text version of the objects, classes, hierarchy, and connections in a nib file. Hallelujah! Once I dump it to a text file, I can run some shell script or regular expression replacement on the file and pop it back into the nib, right? Nope, sorry! The man page for nibtool lists only one bug, and it’s a big one:

You cannot regenerate a nib file using the output of nibtool.

The fact that this is listed as a bug has always given me hope that it would one day be fixed. Unfortunately, it appears to be getting fixed at the same pace as all the other bugs and quirks of IB. But when Michael McCracken posted this entry describing a happy discovery in the latest Xcode release notes, I shared his cautious optimism. Nibtool has been updated, and it includes a new pair of options for “importing” and “exporting” object properties from and to an existing nib file:

Nibtool can extract properties into a plist format that can be edited and then reimported into the nib using the new –export (-e) and –import (-i) flags.

Is this it? The holy grail? I have played around with the new nibtool a little bit, and all I can say is we’re closer, but this is by no means an easy or complete mechanism for manipulating the contents of a nib file. The release notes and man page are so vague that it’s difficult to even figure out how to use the new options. I have mostly deciphered the new behavior, and hopefully can clarify things for you by paraphrasing what the new options do.

In each case, the effects of the tool are only to manipulate the instance objects in the particular nib file. The functionality is therefore useless for adjusting things like connections, hierarchy, etc. Think of the nibtool import and export functions as shorthand for “iterate the objects in my nib and do keyed value reading or writing on them.” I think one of the confusions is that the PLISTFILE argument in each case is an input file to nibtool. Output is always to the standard output.

nibtool -e – Takes as input a special-format plist file that specifies a list of Objective-C class names and associated keypaths for values that should be fetched. For all objects in the nib that are of the specified class, this option produces as output an plist file containing the current keyed values for whatever keys you specify in the PLISTFILE.

nibtool -i – Takes as input a plist file of the format produced by nibtool -e. For every object specified in the input plist, this option essentially looks up the object in the nib and sets its value as specified in the plist.

So in a nutshell, you use nibtool -e to fetch a bunch of values as a text file. Tweak the text file as you see fit, and feed it back in with nibtool -i. A major drawback to nibtool -e is that if any object you specify doesn’t respond to the particular key, then the whole deal is called off. So you can’t for instance ask for “bounds” of all “NSObject” and hope to get just the objects in your nib that actually have a bounds property. You have to make sure that for whatever class you specify, every instance is key-value compliant for the given keypath.

So what is the magic format of the first input plist? It’s not too special, and is documented in the nibtool man page, but it is still clunky enough to limit casual use. You basically cannot use nibtool -e for anything useful unless you’ve already taken the time to put together a suitable “input plist” describing what it is you’re looking for. I knew that at the very least I was going to need something to make the input plist easy, so I started with a small python script that produces a plist for the simplest possible fetch: “given one class name and one keypath, fetch the values for all suitable objects in the nib.” This is ugly but it’s my very first python script. Cut me some slack. (Thanks to Gus Mueller for telling me about the os.popen() function, and to Mark Rowe for teaching me about Python’s magic triple-quoting).

#!/usr/bin/env python

import sys, os;

def usage():
    print 'usage: %s   ' % sys.argv[0]
    print 'produces a PLIST file from  suitable for editing'
    print 'keyPath value of all objects of class '
        
# Mild validation of the arguments
if len(sys.argv) != 4:
   usage()
   sys.exit(1)

# Construct a suitable temporary plist for the desired class and keyPath 
newXML = '''\
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>%s</key>
    <array><string>%s</string></array>
  </dict>
</plist>''' % (sys.argv[2], sys.argv[3])

# write it to a temporary file
xmlFileName = '/tmp/com.red-sweater.pythonClassValueHack.plist'
output = open(xmlFileName, 'w')
output.write(newXML)
output.close()

# now run nibtool feeding it the xml file and pointing it at the nib
myResults = os.popen('/usr/bin/nibtool -e ' + xmlFileName + ' ' + sys.argv[1])
sys.stdout.writelines(myResults.readlines())

Paste this code into a python text file and run it from the command line. I called my NibValFetch.py. Now when I cruise over to one of my nib files, I can do something like this:

./NibValFetch.py Preferences.nib NSTextField frame > hacking.plist

That is: get all the NSTextFields in Preferences.nib, and ask for their frame. The file “hacking.plist” now contains a list of all the NSTextField objects in my nib, along with a text representation of the frame for said field. Now if I wanted to make every text field 2 pixels wider or something, I could write a script to modify the hacking.plist file, and then feed that into nibtool -i.

I don’t really have time to delve into this much more right now. I’m not even sure the new functionality will prove very useful, but I figured that somebody out there will find a good use for it if nudged in the right direction. I hope this introduction to the new functionality clears up what it can and can’t do, as well as giving you a clue as to how you might go about leveraging it. If you think up any really clever hacks with it, please be sure to share in the comments!

8 Responses to “Pain in the Nib”

  1. Cameron Hayne Says:

    Excellent summary of the new capabilities. It is now much clearer what this tool can do – you saved me a bunch of time trying to figure it out myself.
    Thanks.

  2. Jonathan Wight Says:

    Hey Daniel, you’re probably fine with your code as-is but if you want construct a plist from python instead of hard coding it I have some code at http://svn.toxicsoftware.com/public/trunk/Python/Utilities/PropertyList.py that can read/write Apple XML format plists. Even easier you can just import plistlib (included with the system).

  3. Daniel Jalkut Says:

    Cameron: Glad you liked it! Thanks for the feedback.

    Jonathan: Thanks I will have to keep your code in mind if I end up doing more elaborate tricks with plist-generation. Mark Rowe pointed out to me that the entire plist construction line can be reduced to just this line if I use plistlib:

    plist = plistlib.Plist(**{sys.argv[2]: [sys.argv[3]]})

    I think that’s a pretty awesome example of why I should start learning more about Python :)

  4. Sanjay Samani Says:

    There seems to be growing concern in the blogosphere about the shortcomings of XCode and IB as compared to Cocoa. I completely agree that the criticism is justified and whilst finding work arounds like this is fantastic, it doesn’t get around the key issues, e.g., XCode’s workspace is just not as configurable and polished as Project Builders was, IB has not developed over the last few years and greate features like Bindings are poorly supported by its graphical goodness. I think most attendees at WWDC will be looking forward to big steps forward in Xcode and IB and are probably less concerned about new features in Cocoa (as opposed to big Cocoa bug fixes and improved documentation).

  5. Jon Hess Says:

    Daniel: Great write up.

    There’s a very subtle, yet powerful, aspect to this feature. In addition to the import and export actions, nibtool also supports a “–bundle pathToMyBundle” argument. When nibtool encounters this argument, it loads “pathToMyBundle” before loading the specified document. With objective-c categories, this really opens the door for things like customized layout validation.

    Imagine a bundle that defined a category method on NSView like the following

    /// Composed in comment window, YMMV.
    @implementation NSView(GeometryValidation)
    – (BOOL)isClippedBySuperview {
    return [self superview] && NSIntersectsRect([self frame], [[self superview] bounds]);
    }
    @end

    Now, with this simple input plist, you can easily construct a script to preform basic layout validation.
    {
    NSView = (clippedBySuperview);
    }

    I’m sure there are many more interesting combinations of the –export and –bundle arguments.

  6. Daniel Jalkut Says:

    Wow – very interesting point, Jon. I will have to play with that a bit, but you’re right: the bundle should give you access to ensure that whatever accessor you wish was there … is there!

  7. Schwieb Says:

    You can thank the Microsoft Ireland localization team for this partial feature add. :) I *begged* Apple on their behalf for a way to edit nibs in some scriptable fashion so that we don’t have to launch IB just to resize a control. Yep, it would preferably let us manipulate any and all properties of the items, but Apple didn’t go that far. Perhaps in Xcode 2.4 or whatever is next…

  8. Daniel Jalkut Says:

    Wow that’s great! In that case, you guys should definitely complain more often :)

Comments are closed.

Follow the Conversation

Stay up-to-date by subscribing to the Comments RSS Feed for this entry.