Size Matters: Fixed-Height/Width Windows in Interface Builder

Has anyone else seen this?

Make a new Cocoa application project in Xcode 2.1. Open the MainMenu.nib file. Show the inspector for the window in that nib, and switch to the Size panel. Try to make the minimum and maximum sizes for the window the same, either by typing in numbers or hitting the “Current” buttons.

Can’t do it, can you? Whenever you try, Interface Builder automatically “fixes” the maximum value so it is 1 greater than the minimum value. The hell? What if I want the window not to be able to resize vertically or horizontally? It does this for every window, in every nib.

And while 1 pixel may not seem like much, it is definitely visible to the end user, which makes the whole interface look unpolished.

The workaround is to set the min and max values to the same thing programmatically, but that’s a pain in the ass, eh?

I didn’t see anyone complaining about this on the Apple mailing lists. Did this used to work? Am I missing something? Is this just a small enough deal that people live with it? (I’ve filed a bug.)

It also doesn’t look like I can make a little utility based on nibtool to fix this after I’ve made the nib in Interface Builder; while nibtool can show me the offending min/max values in text form, I can’t modify the text, then feed it back into nibtool to generate a changed nib.

I am using the Cocoa UI APIs and Interface Builder again after a hiatus, and I’m finding far more of these workarounds necessary than I would like for a “best of breed” technology.

Don’t Use the Brown Project File

I have an old project that I dusted off today that uses the Omni frameworks. It’s old enough that I remember a time when the frameworks built without a hitch.

Then they stopped building. I searched on the Omni Mailing Lists page and found a variety of build problems people were having.

But the message I needed to read was this one, which says “Even if you’re building with Xcode, you may want to stick to the .pbproj projects….” Turns out the *.xcode files are outdated, despite the fact that it’s a newer project file suffix. With the *.pbproj files, everything builds fine.

Should it have occurred to me to try the older files? Sure. But a Readme note, or removal of the outdated project files, or some update in the six months since this was last released, beyond an obscure mailing list message….would’ve saved me some trouble.

Saving My Bacon

I never used to be a fan of auto-saving.

The last time I really experienced it was with Word, probably back on OS 9. The documentation for Word said that auto-saving would make your file bigger, since Word would not save the whole file, but only a log of your most recent changes. And there was a noticeable pause each time the auto-save occurred that was really annoying.

No thanks, I thought.

But when a perpetrator in my household who shall remain nameless jiggled the power strip enough to turn it off when I was writing a blog post in MarsEdit, after the cursing was over, I did think to myself, “It would be nice if MarsEdit had auto-saved that.”

I hadn’t read the MarsEdit documentation, and I hadn’t experienced any pauses while I worked, so I had no inkling that MarsEdit was in fact auto-saving my drafts.

When I reopened MarsEdit, it notified me that it had detected an auto-saved file, and showed me that file.

Rock on!

A brief survey of the MarsEdit documentation doesn’t reveal anything about this feature. I should know this, but can Cocoa do this automatically now? Is it so mundane that it doesn’t warrant mention?

More interestingly, does the auto-save only kick in when there’s a lull in user activity? That would be cool. Otherwise, I guess I can chalk up the paucity of pauses to faster processors and hard drives.

File Imprecations

There’s no dedicated widget in Cocoa for specifying and displaying a file.

Both Cocoa and Carbon apps tend to cobble together custom UI for the task. A “Save” or “Open” button that triggers a Navigation Services dialog, plus a path text field, and sometimes an icon. Apps tend to be internally consistent – CodeWarrior uses the same aggregate widget everywhere, for instance – but none of them quite match each other.

Cocoa apps often do let you edit the path by hand. It seems to me that this reflects the greater NeXT reliance on the command line, and I can see the utility of it sometimes. But I dislike the potential to erase a path by hitting the delete key by mistake. It also seems to me that this sort of thing is better suited to developer apps, where the user can more safely be assumed to have command line expertise.

Carbon apps, with the old Toolbox deprecation of file paths, generally never allow users to type in the path of the file they want: you must use the Open or Save dialogs to specify the file, though the application might then show you the path afterward.

I can actually see something in-between: maybe in the Save/Open dialog, there is a text field, which reflects the full path of the file/folder you’ve currently chosen. You can type in it, and once a name matches, the Navigation Services portion of the dialog moves to that location specified by your typing. Maybe that’s overkill, but it is the kind of marriage of command-line and UI that I’d really like to see. Toss in tab-completion, and you’re all set!

I’m thinking about this because I’m thinking about how I would modify the UI of TADS Workbench for Macintosh if (more like when) I decide to take over its maintenance. I had my own widget in my Isthmus framework to specify and show a file – I even had a specialization of that widget to deal with special file paths, like the “{Compiler}” and “{Project}” paths in CodeWarrior. I will either have to redo that work for TADS Workbench, discard the concept completely, or – most likely – figure out a more scaled-down version. After all, it was trying to make things perfect that doomed Isthmus.

File Calumnies

Now that I’m newly thinking about helping out with another project in Cocoa, it’s getting me thinking about some issues I’ve had with Cocoa.

Let’s start with files.

Here’s a demonstration for you. Open TextEdit and save a file to the desktop. Go to the desktop and move the file somewhere else. Then go back to TextEdit, modify the file, and save it again.

Presto! New file on the desktop.

Do the same thing in BBEdit. No new file appears on the desktop. Instead, the changes are made to the moved file.

Now, some of you are saying, “Ah ha, bug in TextEdit!” Others of you are saying, “Ah, ha, weird behavior in BBEdit!”

This schism is described in detail in the macosx-dev mailing list thread “Paths, FSRef, Alias, Resource Fork, and the Big Schism”.

As an old Mac user, I think the BBEdit behavior is correct – just because you’ve moved a file doesn’t mean it’s in any way different. The most effective argument to the contrary is that many computer users aren’t used to this behavior, because of experience with Windows and Unix-like OSes.

Another argument in the thread is that, in certain circumstances, this isn’t the behavior you want. For developer projects, for instance, you often want the IDE to refer to projects by paths relative to the project or a particular location. Moving a file doesn’t mean changing the path, it means removing that file from consideration in the project.

I had already gone down this route with my now-abandoned Toolbox-based MacTADS. Writing your own framework does give you the freedom to, say, come up with your own, appropriate file classes.

But I do think that, apart from those special cases, I as a user would rather have the BBEdit behavior. So it distresses me (a) that Cocoa does not have a real file class, and (b) that I so often see NSString substituting for a file reference.

Golfing Another Round: More Cocoa Links

Here are more links to Cocoa Web sites or pages to add to the first batch I presented a couple months ago.

The full list now has permanent home, http://umbar.com/macdev/lists/cocoa.html, on this weblog’s companion site, Umbar.com.

Enjoy!

Cocoa Bindings simple tutorials from Apple:
http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaBindings
I’ve seen developers talking on the mailing lists about issues when using Cocoa bindings for more complicated tasks, so don’t consider this the last word.

Harmless Cocoa, some apps, utility and UI classes, and a little font metrics tutorial:
http://harmless.de/cocoa.html

Knowing Your Boundaries: C++/Objective-C with the Membrane Library

Apple’s preferred framework for writing new Mac OS X applications is Cocoa. Most cross-platform application development for the Mac not done by Apple is done in Carbon.

One reason for this state of affairs is that most large apps for the Mac are legacy apps, originally written for Mac OS 9 and earlier using the Toolbox. These large, flagship apps, such as Photoshop and Microsoft Office, are very important to the platform and with any luck won’t be going away anytime soon. For that reason alone, Apple must stick with a dual-API strategy.

But the second reason has to do with a different kind of history: the history of languages.

Apple’s OO language of choice is Objective-C, thanks to its NeXT heritage. Most cross-platform application development which isn’t done in scripting languages or Java is done in C++. Integrating large C++ and Objective-C codebases is just too hard. C++ and Objective-C code can be mixed, but their error-handling mechanisms and resource allocation traditions are incompatible, so there are no high-profile, large-scale examples.2 It’s easier to write (or buy, thank you, Metrowerks) your own C++ layer over Carbon, and stick with a single set of mechanisms.

Until now.

Well, more accurately, until June of 2003. That’s when Mac Murrett and I introduced the Membrane library to MacHack 18, along with a paper and sample code.

As the paper states in its introduction:

C++ and Objective-C can be used in the same codebase, but their error-handling mechanisms don’t mix well without some extra effort. This means careful resource allocation on both sides and translation code at every boundary between them. Sound like too much work? This paper describes the Membrane C++/Objective-C library, which makes these steps as easy as possible – often simple one-liners – while still both allowing for great flexibility and encouraging rigorous and systematic error-handling policies.

Membrane allows you to create rules for converting C++ exception types to Objective-C NSExceptions and vice versa. It allows you to encapsulate objects from one language in another so deallocation is automatic. This means you can develop the right way, fast.

For small codebases, this is not necessarily a big win. If you only call a few C++ APIs in your Cocoa application, you can just throw some try/catch blocks around them and be done with it. Alternately, if your C++ code doesn’t use exceptions much, you won’t need to convert them with Membrane.

So the conditions where Membrane will be really useful are: large codebase, modern C++ practices.

Working on such a project? Then go to the permanent Membrane Web page at http://umbar.com/membrane and have a look.


1Yes, there are exceptions, the biggest of them being iTunes. Funny coincidence that it’s the only new Apple app ported to Windows, eh?
2The exception to this statement being Safari and the C++ KDE codebase. But the KDE codebase is old and rarely uses such modern C++ best practices as exceptions.

Cocoa Links, Part of a Good Breakfast

For a while now, I’ve been meaning to put together some good lists of links for Mac developers.

Here’s my first one, a list of Cocoa sites.

It’s based on a list from Paul Gobble of StudioBox, Inc. Thanks, Paul! I’ve edited the list and expanded it with my own contributions.

I’ll be adding to this in the future, as well as finding a permanent home for it. Feel free to suggest your own additions in the comments or, once they’re turned off, by emailing me. Enjoy!

Big Nerd Ranch, classes, books, and online resources, by Aaron Hillegass and others:
http://bignerdranch.com/

Blackhole Media BSD-Licensed Goodies:
http://blackholemedia.com/code/

Borkware LLC, some projects with source, MacEdition links, etc., by Mark Dalrymple and others:
http://borkware.com/

CamelBones, an Objective-C/Perl bridge framework, by Sherm Pendley:
http://camelbones.sourceforge.net/

Cocoa Dev Central, articles for “Cocoa newbies”:
http://cocoadevcentral.com/

CocoaDev, an OS X developer wiki (community-edited info site):
http://cocoadev.com/

The Cocoa Files, writings by Andrew Stone:
http://stone.com/The_Cocoa_Files/

Cocoa Literature List, by Jeff Biggus:
http://osx.hyperjeff.net/reference/CocoaArticles.php

Deep Cocoa, OpenGL tutorials by Katherine Tatters:
http://zerobyzero.ca/~ktatters/

Karelia Software‘s Cocoa Open Source:
http://cocoa.karelia.com/

MacDevCenter.com, articles, focus on Open Source, by O’Reilly:
http://macdevcenter.com/

Mamasam, a Resource Center for Cocoa Developers
Searchable archive of Apple’s cocoa-dev and OmniGroup’s MacOSX-dev mailing lists, and other resources.
http://cocoa.mamasam.com/

The Omni Group developer mailing lists:
http://omnigroup.com/developer/mailinglists/

Note the Omni Group does the Right Thing with its mailing lists and provides compressed archives for previous years’ messages. Apple? Hello? If you want such a feature yourself for Apple’s lists, see my rather high-bandwidth solution (original link and update).

Ranchero Software Weblog:
http://ranchero.com/

More Ranchero-provided Cocoa samples can be found at http://ranchero.com/cocoa/.

Unofficial cocoa-dev FAQ, by Alastair Houghton:
http://alastairs-place.net/cocoa/faq.txt

Double or Nothing: Pitfalls with NSTableView’s doubleAction

One of the core capabilities of Interface Builder, when used as the resource editor of a Cocoa application, is connecting UI widgets to code with just a bit of click and drag. The code that is connected to consists of outlets and actions.

Outlets are straightforward, just pointers to objects. The basic thing to remember, as with all Interface Builder connections, is that you shouldn’t try to refer to them in your init methods; use awakeFromNib instead.

Actions are methods, more specifically methods referred to by name (in the nib file) or selector (at runtime) instead of (as in C/C++) by pointer. To connect an action in Interface Builder, you control-drag from the UI widget that will send the action message to the object that will receive it, and then you choose the method in that receiver whose message will be sent.

The reason actions are more complicated than outlets is because actions are really part of a target/action pair. Interface Builder needs to remember more than just the message, it needs to remember the target to send that message to.

Until recently, to me at least, the Interface Builder UI seemed to be a little fuzzy on that distinction. In the Info window for an object, under the Connections pane, there was an Outlets column and an Actions column. Under the latest Interface Builder in Panther, what was called Actions is now referred to as “Target/Action”, which states the actual situation more explicitly.

Another thing to keep in mind is that the Cocoa framework has the action/target pair functionality built into Interface Builder solely for itself. You can’t make your own method/object pointer pair variables in a class and set them via the same Interface Builder UI that Cocoa uses for action/target; there’s just no way to tell Interface Builder to treat your variables that way.

With me so far? Good, because it’s about to get a little more complicated.


A while ago, I made a specialized outline view. When you double-clicked some of the cells, instead of getting the usual text editor, for certain columns and under certain circumstances, you would get more specialized behavior, such as a popup menu or a sheet with multi-line text editing capabilities.

The way to get this behavior is (a) turn off the regular editing behavior for the cell by having the delegate for the outline view return NO for the method outlineView:shouldEditTableColumn:item: and (b) send the message setDoubleAction: to the outline view with, as its parameter, the selector to the method you want to use to invoke the custom editor.

Those of you who know how this works already will protest, “Hey, you missed a step!” And indeed I did. But the above steps work under special circumstances, and they work because of how target/action pairs work.

The control-drag that I mentioned for actions at the top of this post fills in two variables in NSControl and its subclasses. It fills in the target variable with a pointer to the object. And it fills in the action variable with the selector to the method. Now, doubleAction is action‘s cousin in NSTableView. It works exactly the same way. But you can’t control-drag to connect it via Interface Builder, you need to set it via code. So, you would probably guess correctly that setDoubleAction: only sets the method, not the target, and you might look around in vain for setDoubleTarget:. Nope, doesn’t exist. Instead, it also uses target!

Why would this work even when you haven’t set target to anything? Well, if the target isn’t set, the Cocoa framework tries to make a guess what the target should be. You can see the order in which it checks here. This order doesn’t mention the delegate for a control, but NSTableView may be a special case since not all controls have delegates. In any case, it’s a good idea to send a setTarget: message explicitly.

There you have it. As with the solution to most pitfalls, esp. in Cocoa, the solution is very simple, but might not be easy to come up with on your own. Enjoy!