How to Make an Accessible Multiline NSMenuItem (without any custom drawing)

In developing a new application, I ran into the need for a NSPopupButton for selecting a preference. The menu that you see popup when you click on a NSPopupButton is really just an NSMenu full of NSMenuItems. The same menu items that you might see in a contextual menu or even from the main menu at the top of the screen.

I was looking for some way to display a Title as regular, then right under it some kind of subtitle. Naturally, NSMenuItems are designed to handle just a single title. It seemed like if I wanted to display anything other than a single title, I was going to have to take advantage of NSMenuItem’s new ability to host a generic NSView.

Problem

Getting your custom NSView in to a NSMenuItem is easy. I actually got it displaying rather nicely. The problems came when I wanted my NSView and NSMenuItem to behave like they would as per regular.

  1. Passing clicks from my NSView to the NSPopupButton proved to be rather difficult. Moreover, it felt like a huge kludge as it tied these items to a single control. I also wanted to reuse these in the menu up top.

  2. Even with the kludge, I decided to proceed on. Next up was displaying the checkbox (NSOnState) image. For some reason, the checkbox image returned by [NSImage imageNamed:NSOnStateImage] is different from the checkbox image used in the rest of the system. It was skinner and looked out of place.

Overall, making a custom view with this much kludge and not quite feeling right grated on me. I wasted 2 days to get this far, but it doesn’t live up to the user experience that I want to give users and expect from software on the Mac.

Solution

In my rush to get my NSMenuItem running, I overlooked a a critical feature: NSMenuItem titles can take an NSAttributedString.

When initializing your NSMenuItem we can use an attributed string to set our colors. \n also works for creating new lines.

// assume we have an NSMenuItem alloc'ed/init'ed named item.
// we also have a model objected that responds to the "title" and "subtitle" messages 
// named myObject

NSString *title = [NSString stringWithFormat:@"%@\n%@",[myObject title],[myObject subtitle]];
NSMutableAttributedString *attributed_title = [[NSMutableAttributedString alloc] initWithString:title];
        
// This is the system default for controls. anything else and it looks off
NSDictionary *title_options = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont fontWithName:@"Lucida Grande" size:13.0f],NSFontAttributeName,nil];

//make our subtitle a different color as it is just auxillary information 
NSDictionary *sub_title_options = [NSDictionary dictionaryWithObjectsAndKeys:[NSColor disabledControlTextColor],NSForegroundColorAttributeName,nil];

// apply our color attributes to the ranges of the string they are applicable to...
[attributed_title addAttributes:title_options range:[title rangeOfString:[myObject title]]];
[attributed_title addAttributes:sub_title_options range:[title rangeOfString:[myObject subtitle]]];

// finally set our attributed to the menu item
[item setAttributedTitle:attributed_title];
[item setRepresentedObject:myObject];

// clean up
[attributed_title release];

With the above in place we are halfway there. Our new shiny menu item looks great and it fits in. Most importantly there is zero kludge. Actions go where we want them when we want them. The NSOnState image looks like it does in the rest of the system. Life is hunky-dory.

Our multiline NSMenuItem

Until we highlight it.

Our title and state images turn white when highlighted as expected. However, our subtitle…not so much.

Luckily we can fix this without too much trouble. NSMenu’s has a lovely delegate that tells us when it is about to highlight something with it’s menu:willHighlightItem: call. “Will” is the keyword that we tips us of we can use this. It means that our currently highlighted item is still selected, so we can revert any changes made.

*Update: * I had left a huge block of code that smelled bad and left it as an exercise to the reader to clean it up. I decided to remove some of the smell myself. If you have ideas on how the code below could be refactored even better, please let a comment below.

// set our controller to be the delegate of our NSMenu. In the case of a NSPopupButton, 
// you can access it with the [myButton menu] method.

- (void)setForegroundColor:(NSColor *)foregroundColor 
    onAttributedString:(NSMutableAttributedString *)attributedString 
    forRangeOfString:(NSString *)string {

    NSRange range = [[attributedString mutableString] rangeOfString:string];
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:foregroundColor,NSForegroundColorAttributeName,nil];
    
    [attributedString setAttributes:attributes range:range];
    
}

- (void)willHighlightItem:(NSMenuItem *)item currentlyHighlightedItem:(NSMenuItem *)currentItem {
    
    if (currentItem != nil) {
        NSString *subtitle = [(MyModelObject *)[currentItem representedObject] subtitle];
        NSMutableAttributedString *attributedString = [[currentItem attributedTitle] mutableCopy];

        [self setForegroundColor:[NSColor disabledControlTextColor] onAttributedString:attributedString forRangeOfString:subtitle];
                
        [currentItem setAttributedTitle:attributedString];
        [attributedString release];
    }
    
    NSString *subtitle = [(MyModelObject *)[item representedObject] subtitle];
    NSMutableAttributedString *attributedString = [[item attributedTitle] mutableCopy];
    
    [self setForegroundColor:[NSColor selectedMenuItemTextColor] onAttributedString:attributedString forRangeOfString:subtitle];

    [item setAttributedTitle:attributedString];
    [attributedString release];
}

Our nicely highlighted NSMenuItem

Conclusion

With just the few lines of code above, we can create a custom looking NSMenuItem that behaves exactly as we would expect it to. What’s more by using an NSAttributedString isntead of a custom view, our “custom control” get accessibility for free. This means users with impaired sight get the same first-class experience as everyone else. Talk about a win-win-win.

Posted in Accessibility, Cocoa, Hacking, Mac OS X, Objective-C | 3 Comments

Choose the Right Tool (Everything is not a nail)

Picking the right tool for the job is often rather difficult for software developers. As software developers,We tend to find a hammer we like and treat everything like a nail, even if it’s a screw. Sometimes we’re reluctant to learn new and different tools or ways of hammering.

When I first started writing Jisho, my hammer of choice was Objective-C. All projects, no matter their suitability, were developed in Objective-C. I even wished that Apple would have continued NeXT’s support for WebObjects in Objective-C. Afterall, everything is nail.

This meant that things that probably should have never been written in Objective-C were written in Objective-C. That’s not to say that the things I wrote couldn’t be written in Objective-C, indeed, they could, but that it would have been better to pick a different tool. A rock can break a piece of wood in two, which is great if you just need two random lengths of wood, but if you need any more precision, you should use a saw. Objective-C and Python can both parse xml, but the former takes a lot more effort than the later.

I eventually came to my senses and realized that my supporting application was not scaling the way I had hoped. It needed to be rewritten. What did I do? I picked the same hammer again, foolishly expecting different results.

It worked great for a while – until it didn’t. Features only came after great reluctance if at all. I knew it needed to be rewritten, again.

This time around, I have a wider skillset than I did when I first started Jisho. I know a few more scripting languages reasonably well. Some I like a lot more than others.

While I am still in the process of writing the parser, it’s coming together much quicker than the old versions ever did. More importantly, it doesn’t read like a bunch of voodoo magic. These tools excel for things like this. We should use them.

Today, Objective-C is still one of the most important hammers in my toolbox – perhaps even my favourite. But sometimes you need a saw.

As a software developer if you think something would be easier with a different tool, try it and find out. Best case scenerio you’re right and you’ve learned a new tool. Worst case scenario is you’ve learned a new tool and expanded your skillset. You can’t lose.

Posted in Hacking, Objective-C, PHP, Python | Leave a comment

8 Things I Learned About Software Development (Without writing code)

In 2009 I set out to make a major elease of my Japanese-English dictionary Jisho 4.0. Jisho 4.0 was to be a complete overhaul of the existing codebase and a 100% new interface.

After a scrapped interface and a few delays, I forced myself stop adding new features and move the “nice to haves” into a future release. Jisho 4.0 was shipped in December 2009.

Soon after the 4.0 release I began work on Jisho 4.1 to take care of these issues. Jisho 4.1 was released in January 2011. The development time was quite long, but most of it was not spent writing code. Below are some lessons I learned while developing Jisho 4.1.

Define the scope of the release

Define features or fixes in a given release and stick to it. With a proper scope written there will be no second guessing if your program is ready to ship. Have you implemented the planned changes? Yes? Ship.

Keep it limited

If it sounds like it could be broken into multiple releases, it probably should be. Try to keep releases small. I made the mistake of trying to refactor the backend to improve search and add in some new UI features for a single release. The end result was looking 3 major undertakings directly after a major release was paralyzing.

Fully plan an interface before committing to development of a feature

Fully plan how a feature is going to work and integrate with the rest of the application before committing to develop it for a particular release.

With Jisho 4.1, I wanted to refine the interface so the user could see kanji in a larger view. I hadn’t planned the interaction properly and a clean design was not forth coming.

I spent the majority of development time trying various interactions that inevitably felt unnatural. Simply planning this refinement more completely would have told me it’s enough for it’s own release.

Push features to if it means you can release

If you have a feature that is sapping development time and holding up bug fixes or already completed features, push it. There’s no shame in pushing a feature to a future release so your users can get better software today.

Users tend to get upset if you promise a feature for a given release and then pull it. So, this lesson is most applicable if you don’t promise particular features while they are still in development or even before they are released.

Git (or your scm of choice) is a good thing

Creating a branch for development of a new feature is a great way to make it easy to revert to a prior version so you can release. If you only merge branches once the fix or feature is stable and complete, you will always have a stable version ready.

I just started using Git before the development of 4.0. Before that I was coding dangerously with random zip archives of the code, usually at each point release, and a changelog that only mentioned the changes the end-user would see.

Lower the barrier

The more steps required when releasing a piece of software has the same effect as adding steps during checkout. You’re going to lose a lot of people (or in our case inertia) towards making an actual release. Simply your release process as much as possible.

Until 4.1, Jisho was distributed as a dmg for new users. This dmg had a custom background, readme, shortcut to the applications folder. Each release was built by hand. I would spend a good hour crafting this dmg for each release.

Now Jisho 4.1 is simply distributed as a zip file of the application. I still have to zip / upload it manually at this point, which takes 2 minutes, but that’s still an extra step. Future plans include automating the entire process from within Xcode.

Release something

Releasing is like software food. Software lives by releasing new versions. The quickest way to kill a software project is to get weighed down by a single non-critical feature of fix.

The guilt associated with keeping fixes out of the hands of your users in the name of an unannounced feature will only grow with time. Assuming you haven’t pre-announced the feature, users won’t know what they’re missing. If you have, they might complain a bit, but most users would rather have something new rather than nothing.

It’s not too late

If you find yourself agreeing with any of this, stop reading this and figure out the fastest way to get a release out the door. It’s not to late to release something.

Jisho was well on it’s way to death by starvation. By reverting to the latest stable version and cutting the features that were holding up release I was able to breath life back into Jisho.

Was the release technically what one would consider a release worthy of a new sub-number version release? By itself – probably not*. However, the principal still stands – if your software looks like it’s starving, cut whatever you need to and feed it now. Failing to do so may inadvertently cause the death of your project.


* There were other factors at play with the version number, which I written about on the Sugoi Software blog.

Posted in Development, Hacking | 3 Comments

Christmas Time Hakone

As you’ve probably realized by now, I have a thing for the Japanese countryside. Especially the countryside that is modern – yet classic, old – yet new, western – yet uniquely Japanese.

This is the countryside of Japan that you have seen before in earlier posts as when I visited Tochigi. I am glad to report that this modern “Wafu”, or Japanese-style, is spreading and catching on.

This past week I went to a place that I had been many times before, yet never to this particular place. I visited Hakone.

In order to get up the mountain by train you must take the Hakone-tozan Tetsu-do, which zig-zags its way up, passing other trains zig-zagging their way down only at the switch back. The driver actually gets out of the “front” of the train and walks to the back, which becomes the new front.

http://farm6.static.flickr.com/5281/5323073565_ed14867b65.jpg

My destination was only a few stops, but it takes about 20 minutes. The stations are noticeably smaller than those in Yokohama or Tokyo as you would expect. Especially surprising is that there is no gate. Just a single worker that checks everyone’s ticket, or two stands with a IC Card reader for your Passmo/Suica train passes.

http://farm6.static.flickr.com/5245/5323678480_40d1106a0b.jpg

Cafe Naraya is exactly the style that is spreading and that I so enjoy. Wood with white walls, natural colors, and delicious coffee. The cafe actually has a Foot-onsen, or Ashi-yuu, that is available outside for customers. However the day we went it was unusually full of customers sticking their feet in the warm water.

http://farm6.static.flickr.com/5046/5323679228_77340fe982.jpg

As guessed the second story has a small art gallery, showing around 6 – 10 different works of art. The second floor also contains a small library, which you are encouraged to borrow from while you are in the cafe. It had books in all manner of Languages, though Japanese was by far the most dominant.

http://farm6.static.flickr.com/5281/5323073991_875c7ac371.jpg

If you’re going to a cafe, you can’t not mention the food. The food was quite tasty. I had a Cafe Latte and split a hot dog of sorts that was topped with an organic tomato salsa.

http://farm6.static.flickr.com/5287/5323678980_f4455e6d4a.jpg

If you find yourself in the Hakone area, I recommend looking up Naraya Cafe. It’s worth the short jaunt up the mountain. With an ounce of luck, this trend of modern Wafu cafes will continue to spread, maybe even closer to the city.

Posted in Japan, Travel | Leave a comment

Creating Universal Overlays Using UIViews on iOS

What happens when you are developing a UIViewController based application and you want to have an simple transparent overlay for displaying things, say a custom achievements UI in a game, or anything really.

In my case, I had a singleton that handles all interactions with GameCenter, called DAGameCenter. With this, anywhere in my application I could simply report an achievement with a single call:

[[DAGameCenter sharedGameCenter] reportAchievement:@"com.mygame.achievement" 
                                 percent:100.0f];

DAGameCenter then tries to report the achievement to GameCenter and if it fails, archives it in a list of achievements to try reporting at another time. The proble comes in, as you probably guessed it, having a uniform method of displaying achievements to the user across all screens.

My first idea was to simply use a new UIWindow. UIWindow has properties for order the level of each window. I could simply create a new UIWindow, set the level to be above my keyWindow, and simply forward all events to my keyWindow so the user can still interact with the window below.

Problem

Apple advises against creating more than one UIWindow object. Your UIWindow is also already created for you by UIApplication when your app launches. So, things aren’t as simple as we would hope.

We can move up the object hierarchy a little bit to UIView, UIWindow’s parent class. We can simply put this view on top of our main view and all is peachy, right? Something like

OverlayView *myOverlay = [[OverlayView alloc] initWithFrame:frame];
[[[UIApplication sharedApplication] keyWindow] addSubView:myOverlay];

Not quite…simply adding a UIView to our existing UIWindow causes our interface to become unresponsive. This is because our newly added view is capturing all events. As our view doesn’t handle any events, it simply forwards the event up the responder chain, to our UIWindow, which then forwards it to our UIApplication, and then the event is ignored.

Our mainView and our overlayViews are not parent-child but they are both siblings of our UIWindow. This, in effect, creates our overlayView creates a second hierarchy of views in our application.

A quick and dirty solution would be to simply add a delegate to our overlayView and when creating our ourlayView set our mainView as the delegate. What happens when we push a second view above our mainView, say a settings screen. Our settings will become unresponsive unless we also remember to update our delegate.

So, this solution effectively loses maintainability after expanding beyond a single view and requires us to actively remember to set our delegates with navigation. It’s just asking for problems.

Our problems are two-fold: locating the view that is currently being displayed and then forwarding events to said view.

Solution

Forwarding events is quite simple. We simply need to overwrite the hitTest:withEvent method in our view.

It turns out that you can access a given view’s subviews with a simple call to…subviews. This returns an array, that’s helpfully ordered from back to front. UIView has a method that allows us to insert views above other views. UIWindow is a subclass of UIView, so all of this applies to it as well. We can gain access to an ordered array our subviews of our window anywhere in our application by simply calling subviews on our keyWindow.

As I’m sure you can probably guess by now, the solution. We simply need to insert our overlayView above our UIWindow. Then, we can forward events to the back-most subview that our window has.

When we push a modal view on to our UI to change screens from mainView to settings, it actually removes our mainView from our view hierarchy and replaces it with our settings view. This means that the “active” view will always be at 0.

All together, our code looks something like as follows:


// Somewhere in our Singleton. Add our view so it is always above all views in our window
// In my case, I have it in the block that runs when a player is authenticated with GameCenter
-(void)insertAchievementView {
    UIWindow *mainWindow = [[UIApplication sharedApplication] keyWindow];
    if(!_achievementView)
        _achievementView = [[DAAchievementView alloc] initWithFrame:[mainWindow frame]];
    [mainWindow insertSubview:_achievementView aboveSubview:mainWindow];
}

// Then, in our actual achievementView, forward our hitTests
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return [[[[[UIApplication sharedApplication] keyWindow] subviews] objectAtIndex:0] hitTest:point withEvent:event];
}

Conclusion

We can mimic the usage of a second UIWindow for displaying achievements of our application with a simple overlay view. Integrating the appropriate display methods of our overlay view in our singleton allows decoupling of the overlay and the view behind it, greatly reducing complexity. Furthermore, this decoupling allows all views in our application to utilize the overlay “for free”.

Posted in Cocoa, iPhone, Objective-C | 2 Comments