Tagged: Cocoa

Out of Context

Twitter’s been having a discussion about tech job interviews recently: you can see my contributions here, here, here, and here.

For the tl;dr crowd, my take is that interviews are extremely difficult, and so you (and I) should have some empathy.

A while ago, I did a bunch of interviews for a junior iOS developer. I gave them what is apparently a quite common exercise: in a simple iOS app, get some JSON data from a server and use it to populate a table, including, for each entry, a link to an image file to download separately.

I just made such a project, called Numbers, available here.

When you first run it, it shows a table filled with entries 1-20, where the brightly-colored number icons are each loaded separately.

A button to the right of the navigation bar is labeled “Wrong”, meaning you’re currently using the image loading implementation that’s incorrect.

If you scroll to the bottom of the table quickly, you’ll see that initially, the rows you uncover will be temporarily populated with incorrect icons:

Screenshot of "Numbers" application table view, scrolled to bottom, with rows 16 through 20 having incorrect icons 2 through 6.

That’s because, in the incorrect implementation, the async network calls insert the loaded images into the cells that originally requested them.

But, as Everyone Knows, cells in UITableView are reused when you scroll, which means by the time the network calls finish, the original cell might be in use for at a different row, and it shouldn’t display the original row’s contents.

Instead, the network call for an image should update the cell that currently represents the row.

If you tap the “Wrong” button, it will change to the text “Right”, and that’s how the application will behave when it reloads the table. Scrolling quickly to the bottom of the table won’t result in erroneously populated images anymore.

When I was interviewing, if the interviewee said they knew table views, I would give them this exercise, and would consider them not worth hiring if they made that rookie mistake.

Nowadays, it’s clear to me that this is a Gotcha! question like any other.

Instead of it being a question about how they think, how they solve problems, it requires a very specific piece of information you either have it your head at that moment, or you don’t.

In a recent interview where I was debugging a problematic table view implementation, I failed to recognize a similar incorrect image loading mechanism — until nudged to do so by the interviewer. I just didn’t see it. If that interviewer had been as quick to judge as I had been in the past, I wouldn’t have gotten the job.

As a final note of curiosity, you might notice that, in the Numbers project, I reset the NSURLSession each time before reloading the table view’s contents. That’s because NSURLSession has its own cache of network call results, and if I didn’t reset it, you would only be able to reproduce the “wrong” behavior the very first time you tried it. Every subsequent time (including across app relaunches), the images would “load” instantaneously from the cache.

While I would never recommend shipping the wrong implementation, even if you did, these days, Apple’s frameworks would mitigate its impact.

Naming the Beast

I’ve been writing code for a command-line input buffer.

Seems a bit odd in this day and age, eh? OS X already has a command line implementation – several, in fact.

But this one’s for my home project, which is a curious combination of Web browser and command line tool.

Per the Windows port, this command line acts a little odd. You can move up and move down in the command history using the up and down arrow keys, but if you modify any of the previous commands, those changes don’t “take”. Only changes to the last command are remembered if you move up and down the history.

Oh, and the current command – the one you’re looking at on the screen at any particular moment – needs to be held in an external buffer controlled by the cross-platform engine.

I have an NSArray of all the previous commands, and logic to copy the proper entry in that array to the buffer if the user moves up and down through the command history, but that last command line makes things a bit more fiddly. If you move up, I have to remember the contents of it, even though I have to fill the external buffer with new contents. But I don’t want to add it to the history, because you haven’t pressed return yet.

I said “fiddly,” not hard. My particular solution was to add it to the history array anyway, for the benefits I get from reusing the code that copies from the array to the buffer. As soon as you move up, we enter this special “extra entry added” mode, which we leave again if you move all the way back down again, or hit return.

So I knew that whenever the current command index didn’t point to the very end, I was in my mode, but I found myself getting a little confused, not able to nail everything down, even though the code itself wasn’t very complex. I knew what I wanted, but the code didn’t look like what I wanted.

The solution (and the point of this long-winded entry) was all about naming. Instead of the logic looking like this:

if(mCurrentCommandIndex < [mCommands count])

I made it look like this:

if([self bufferTemporarilyAddedToCommands])

and exiting the mode, instead of looking like this:

[mCommands removeLastObject]

I changed to look like this:

[self removeBufferTemporarilyAddedToCommands]

Not rocket science, but it allowed me to see where the code was doing its job, and where it wasn’t, and make the appropriate corrections.

Oh, that, and unit tests.

P.S. Yes, I use PowerPlant-style variable prefixes in my Cocoa code. So sue me.

The Cat That Ate the Rat…

Writing bugs often makes me more productive.

That, or writing requests for help.

For instance, I have a custom NSView, whose contents, mostly text, are completely laid out by a cross-platform engine, i.e. not by Cocoa. So I can’t use NSTextView.

But I want it to act like an NSTextView. For instance, I want to be able to select text just like an NSTextView, and I want a blinking cursor just like an NSTextView.

So I started writing up a query to the Cocoa-dev mailing list about this. But you don’t get much respect, or much help, if you aren’t as specific about what you need as you can be.

So first, I had to determine what I didn’t know. Which meant trying to implement it, and documenting where I got stuck.

Turns out, I didn’t get stuck, at least for the first part.

How do I get started learning about something new? Well, if I can guess an API name, I’ll start with the documentation, but usually I can’t. So I search http://search.lists.apple.com based on the words I do know, like “selected text color”. If there are a lot of false matches from other frameworks, I’ll restrict the search to Cocoa-dev, but sometimes the answer is, say, a Carbon API, so I’m inclusive at first.

I find that the spare, high-level style of Apple’s developer documentation sometimes leaves me scratching my head. Is this really what I need? The mailing lists are great for this sort of clarification.

So I find out the API I need for the selected text background is the appropriately-named:

-[NSColor selectedTextBackgroundColor]

And the post that gave me that also pointed me to the “Accessing System Colors” documentation topic, which helpfully mentioned the notification:

NSSystemColorsDidChangeNotification

which allows me to change the color in my application immediately if the user chooses a different highlight color from System Preferences.

But I wasn’t done because, when an application window is no longer foremost, the selection color changes from the user-choosable highlight color to a gray color.

I was unable to find a specific question about that, but I found another post that talked about matching API colors via the Developer color palette in the color panel. (Cmd-Shift-C in TextEdit.) There isn’t a nonForemostSelectedTextBackgroundColor system color, but there is a secondarySelectedControlColor that matches the non-foremost selected text color in TextEdit, and has a name that at least could apply to my situation.

So I have something that works, and gets its information as much from the system as possible, so it’s as future-proof as possible while still being custom. And I didn’t need to ask any questions! Except…

The same routine didn’t get me an answer as to the best, most system-friendly way to show a blinking caret. I could do the whole thing myself, but that might look weird in the radically overhauled UI in Mac OS X 10.9 “Puddy Tat,” eh? And I’d rather not have an NSTextView whose sole function is to show that caret – seems like a fragile, hacky solution.

So I have a question after all.

Omnipreferential: Rolling My Own Prefs Window, Instead of Using Omni Frameworks

After a long hiatus, I am once again looking at the project that first got me using both the Omni frameworks and unit tests.

Why now? An application already exists that does what my project’s app does, but that existing app is Classic-only. Classic seemed to be moseying along just fine until splat! it hit the brick wall of Macintel. No more Classic.

I was using the Omni frameworks primarily for how they made your application’s preferences window easier to implement. The trouble is, the Omni framework projects are sorely outdated. The version you’re supposed to use don’t have native targets, which is a requirement for building Universal binaries.

So it’s time to give them the heave-ho. Does that mean I have to make my own preferences implementation from scratch?

One alternative is to use the Preference Panes framework from Apple. It’s primarily for System Preferences panes, but the documentation makes it clear that it can be used for application preferences, too. The trouble? If you use it for your own application, you have to manage the window and the toolbar yourself. Since that’s most of the work, and since, if you use the Preferences Pane framework, you need to package each pane as a separate bundle, which is even more work, I give it a pass.

Uli Kusterer has UKPrefsPanel and UKToolbarFactory available in the source code portion of his Web site. They look promising. The prefs panel even uses an NSTabView as an easy way to deal with the prefs panes, which is how I was thinking of doing it. But it doesn’t support automatic resizing of a preferences window to the height of each individual preference pane, which is something I definitely want.

In fact, I want my preferences window to look and behave exactly like the preferences windows of standard Apple applications like Safari and Address Book.

So I decide to work directly with NSToolbar, and see what sort of progress I can make.

Apple’s ADC Web site has ToolbarSample source code that’s a good starting point.

Following that sample, I make the NSToolbar myself in code, and add it to the window by sending the window the message setToolbar:. That API actually expands the window by the height of the toolbar: I don’t have to leave room for it myself in the window’s nib. The window also fully takes care of the user hiding and reshowing the toolbar, and saving the selected toolbar item, as long as I send it a setAutosavesConfiguration:YES message. Neat.

There are a lot of things that need some tweaking, though:

• I want to use an NSWindowController to encapsulate the preferences window nib, a straightforward Cocoa technique. But the controller doesn’t seem to use NSWindow’s frameAutosaveName value, which is settable in the nib. In fact, it seems to set it to nil. Instead, if I want to go that route (which I don’t, see below) I need to set the controller’s windowFrameAutosaveName value, which can only be done in code. Annoying.

• The preferences windows of Safari et al erase themselves and then use a cool drawer-like animation to resize between preference panes. I can get the latter using setFrame:display:YES animate:YES, but not the former. I handle this by adding an extra, empty NSTabViewItem after all the other meaningful ones. I switch to that, do the resize, then switch to the NSTabViewItem I really want afterwards.

• I want the individual preference pane’s size info to be in the nib, not something I have recompile to alter. But every NSTabViewItem within an NSTabView is the same size. I work around this by putting my own custom NSView inside each NSTabViewItem, and then putting all my widgets in that subview.

In my code, I check for that subview’s size, and resize the window to match it, when switching to that NSTabViewItem.

• I need to be careful with resizing: it order to get the the effect I want — which is that the window title bar stays stationary, and the bottom of the window slides up or down — in the normal Quartz coordinate system, I need to change both the window size and its origin, because the origin is in the lower left-hand corner of the window. So, every time I select a toolbar item, as far as Cocoa is concerned, I’m both resizing and moving the window. But NSWindowController doesn’t catch this, leading to erroneous positioning when I close and reopen the application.

• If I handle user defaults manually instead of relying on NSWindowController, I can save the window’s frame to the user defaults (a) when I choose a new toolbar item (see the sample for how to associate toolbar items with actions), (b) when the window is moved around by the user (via the notification NSWindowDidMoveNotification), and (c) when the window is resized (via the notification NSWindowDidResizeNotification). Now, the window is not user-resizable, but this notification is still invoked when the user shows or hides the toolbar, because that automatically changes the size of the window.

But why do that much work? Instead, I listen for the notification NSApplicationWillTerminateNotification, and save off the window frame then. Since user defaults are generally only saved to disk upon application termination, this doesn’t lead to a mismatch even if you force-quit the application, because neither the toolbar user default changes that have already happened, nor my code’s user default changes that haven’t happened yet, are saved.

Authority Issues II: The Dialog That Wouldn’t Die

Last time around, I talked about the Security framework. It is a set of “beneath the hood” C-based APIs you can use from Carbon or Cocoa.

In addition, there’s the Security Interface framework, which is a Cocoa framework of standard security UI widgets.

One of the widgets in this framework is the SFAuthorizationView, which looks like so in its unauthorized and authorized states:
SFAuthorizationView, locked iconSFAuthorizationView, unlocked icon

You tell it which right you want it to use, and it manages an AuthorizationRef for that right. It’s a very useful widget, but it has a gotcha.

You add an SFAuthorizationView to a window in your nib file by doing the following:

1) Drag the SecurityInterface/SFAuthorizationView.h header to your nib.

2) Make a custom view.

3) Change that custom view’s custom class to SFAuthorizationView.

Here’s some code for a delegate class for such a window. It assumes the delegate has an SFAuthorizationView outlet called “authorizationView”:

- (void)awakeFromNib
{
   [authorizationView setString:"right.to.party"];
   [authorizationView updateStatus:self];

   [authorizationView setFlags:
      kAuthorizationFlagInteractionAllowed |
      kAuthorizationFlagExtendRights];

   [authorizationView setAutoupdate:YES];
}

The first two messages are necessary to set up the authorization view. Without a specified right, the view can’t function. Without the updateStatus: message, it won’t show the lock icon correctly.

You’ll notice that the value for the setFlags: message is the same value I used for the second AuthoriaztionCreate)() function call in my last post. It serves the same purpose here that it did there.

But it’s the last call that’s the gotcha. You send the message setAutoupdate:YES if you want the authorization view to automatically change its appearance and behavior when, for example, an authorization expires, which they normally do after 5 minutes. So far so good.

But if that is combined with the flag kAuthorizationFlagInteractionAllowed, and if a user decides to cancel out of an authorization dialog instead of typing in a valid password…the then dialog comes back. Cancel it again, it comes back again. If you don’t have the password — say, for example, you’re not an adminstrative user for the Mac in question — the only way to get the dialog-that-won’t-die to go away is to force-quit the entire application.

Not good. There are two workarounds for this gotcha:

1) Don’t use kAuthorizationFlagInteractionAllowed. This means that when an update occurs, for example an authorization times out, the user won’t be prompted to authorize again immediately. Instead, the lock icon will just go ahead and lock itself. There isn’t a “nyah nyah” sound, but there might as well be.

2) Don’t set your view to autoupdate. You might do this if you’re using a right that, say, never expires.

For anyone using or thinking of using the SFAuthorizationView widget, I hope this helps.

Authority Issues: Gotchas with the Security Framework

You see those pesky authorization dialogs every so often, right? "Finder requires that you type your password." "BBEdit requires that you type your password." This means the application involved is using the Security framework to perform an action with, say, administrative privileges that it wouldn’t normally have.

If you turn down the Details disclosure triangle in such a dialog, you see the name of the app and an arcane Requested Right string.

These rights should be added to what’s called the "policy database," stored in the file /etc/authorization, when the application that wants to use them is first installed. For a full explanation, see Performing Privileged Operations With Authorization Services, but for this post, I’m interested in the API you use to add those rights.

Here’s the example given in the AuthorizationDB.h header in the Security framework.

OSStatus status =
   AuthorizationRightSet(
      NULL,
      "com.ifoo.ifax.send",
      CFSTR(kAuthorizationRuleIsAdmin),
      CFSTR("Authorize sending of a fax"),
      NULL,
      NULL);

Looks reasonable, right?

Bzzzt. Sorry! If you actually try to run this code, at least on Mac OS X 10.4 "Tiger," you get a −2147418108 error. What’s a −2147418108 error? No clue. But it’s not good.

The first parameter is of type AuthorizationRef. You get a valid authorization reference if the user types her password into one of those dialogs, but you don’t need special permission to add a new right to the policy database. Hence the null value in the example. But it doesn’t work.

Here’s what you need to do instead:

AuthorizationRef authorization = NULL;

OSStatus result =
   AuthorizationCreate(
      NULL, NULL,
      kAuthorizationFlagDefaults,
      &authorization);

if (result == noErr)
{
   result =
      AuthorizationRightSet(
         authorization,
         "com.ifoo.ifax.send",
         CFSTR(kAuthorizationRuleAuthenticateAsAdmin),
         CFSTR("Authorize sending of a fax"),
         NULL,
         NULL);

   AuthorizationFree(
      authorization,
      kAuthorizationFlagDefaults);
}

AuthorizationCreate() actually has a whole bunch of options you can use, but here I’m using it with minimal values to create a blank, good-for-nothing AuthorizationRef. Even so, that’s good enough for AuthorizationRightSet(). With this code, there’s no −2147418108 error.

So we’re done? Not yet!

You’ll notice above that I changed the third, "rule" parameter of AuthorizationRightSet() from the kAuthorizationRuleIsAdmin in the original sample to kAuthorizationRuleAuthenticateAsAdmin. With that rule, you are shown an authorization dialog when I use AuthorizationCreate() like so:

AuthorizationItem myItems =
   { "com.ifoo.ifax.send", 0, NULL, 0 };

AuthorizationRights myRights =
   { 0, &myItems };

AuthorizationFlags myFlags =
   kAuthorizationFlagInteractionAllowed |
   kAuthorizationFlagExtendRights;

AuthorizationRef authorization = NULL;

OSStatus result =
   AuthorizationCreate(&myRights, kAuthorizationEmptyEnvironment,
      myFlags, &authorization);

This call’s a lot more complicated, eh? But it boils down to trying to get a valid authorization for the right we added.

Here’s the dialog you see in response:

Authorization dialog with erroneous caption

Oops! If you fill in the fourth, "description key" parameter of AuthorizationRightSet() with a string as shown in the header example, that "description key" is concatenated with the default system prompt in any authorization dialog that refers to that right.

Passing NULL for the "description key" parameter uses just the default system prompt, which works much better.

Now we’re done.

Banana Split: Overcoming Problems with NSSplitView

There are a bunch of bugs or misbehaviors in the current (10.4) version of NSSplitView. Here are the ones I’ve needed to solve:

(1) While the split view respects the minimum and maximum coordinates if you drag the divider around yourself, it doesn’t respect them if you’ve set up the view to resize with the window, and you shrink the window.

(2) If you collapse a subview, any view in it that has keystroke focus retains it. You can still type in invisible text fields, navigate invisible tables, etc. Plus, all the text fields, tables, etc. inside the collapsed subview can still be tabbed through.

(3) The coordinates of the subviews are not automatically saved and restored, the way the column sizes and positions in a NSTableView are.

In my recent project, I fixed all these problems with delegate methods. If you implement splitView:resizeSubviewsWithOldSize: and do the resizing yourself in the problem case, you can fix (1). If you implement splitViewDidResizeSubviews:, you can do (3) and, if you check for when a subview has collapsed, you can make sure (2) doesn’t happen.

However, if you want to write less of your own code, there are other options. Even with a cursory search, I have found three NSSplitView replacements:

OASplitView from the Omni frameworks. Is a subclass of NSSplitView. Solves only (3).

KFSplitView from Ken Ferry. A subclass of NSSplitView. It solves (1) and (3). (2) still happens in its demo.

RBSplitView from Rainer Brockerhoff. Not a subclass of NSSplitView. Solves (1) and (3). Solves the first part of (2), but not the second.

Admittedly, the second part of (2) might not be possible in general-purpose framework code, since it can involve an invasive solution. I solved it in the code I was working on by subclassing the NSTableView located in the subview. In the subclass, I added an _acceptsFirstResponder flag, a setAcceptsFirstResponder: method to set the flag, and overrode acceptsFirstResponder to use the flag.

With these changes, it was easy to take a table out of the tab chain when the subview was collapsed, by merely sending it the set message. It would have been more onerous if I had to subclass multiple classes to get this same behavior in different types of widgets, in order to send the same set message in them all.

You may be able to get the same behavior out of some controls by sending them a setEnabled: message, but that wasn’t successful for me with tables.