Porting Paprika from iOS to Mac: a technical retrospective
Last month we managed to finally ship Paprika for Mac and I figured now would be a good time to share our experiences porting an app from the iOS platform to the Mac platform.
It took way longer than we expected
They always say this about software timelines and estimates, but we figured we had a pretty decent handle on the difficulty of the task at hand, knowing how long the iPhone/iPad versions of Paprika took to develop. The language was the same, many of the APIs were the same, how hard could it be? Right?
Well, we thought it would take about 3 months to complete the Mac app, and it ended up being closer to 6. Although many of the APIs were the same between UIKit and AppKit, the differences that existed slowed us down quite a bit.
Ultimately, this came down to AppKit being a lot older and not being as refined as its iOS counterpart, and not offering as much functionality that we simply got ‘for free’ on iOS. Further details below:
Technical challenges
NSFetchedResultsController doesn’t exist.
If you’re a multi-threaded Core Data app, NSFetchedResultsController is pretty useful. It gives you automatic section management and automatic tracking of object/section changes (from the NSFetchedResultsControllerDelegate).
Unfortunately the AppKit equivalent, NSArrayController, doesn’t provide either of these features. This means that if you want to create sectioned table views (common on iOS, not so common on Mac), you have to maintain your table data manually. It also means that if you want to refresh your table when a managed object changes, you have to be manually listening to NSManagedObjectContext change notifications and handle the refresh yourself.
Bindings are sometimes useful (but not for heterogeneous data).
In theory, bindings are supposed to make populating table views and outline views a lot easier than doing it all by hand. This is great if you’re populating a table view straight from a database table and can hook everything up with three lines of code and a few Interface Builder settings.
But if you’re attempting to display heterogenous data in a single table view or outline view, bindings go out the window and you’re stuck implementing NSTableViewDataSource yourself.
Creating custom NSTableViews sucks, especially pre-Lion.
NSTableView is great for displaying actual tabular data in the default chrome, but if you want to create a custom list view (aka the kind you commonly see represented in an iOS UITableView), rendering your own table cells is a massive pain in the ass. Subclassing NSCell/NSTextFieldCell is some kind of crazy black art and not intuitive at all.
Because of this, developers have gone to great lengths to create alternate implementations that behave more like UITableViews. We tried both but weren’t quite satisfied with either.
Fortunately, Lion introduced the concept of View-Based Table Views which made our lives a bit easier, but it still lacks the section support and NSFetchedResultsController integration that made its iOS counterpart so clean and easy to use.
NSViewController doesn’t do anything.
Compare the docs between NSViewController and UIViewController. UIViewController hooks into the view lifecycle, notifies you whenever your views load, unload, appear, and disappear, and all sorts of other useful things. NSViewController helps you load a view from a nib…and that’s pretty much it. It doesn’t have the great hook points like viewDidLoad/viewWillAppear unless you want to do some crazy swizzling. You also have to do some funny hijacking if you want it to be part of your first responder chain (which you do, because you need to listen to menu bar and keyboard events).
Sending an email is… harder than it should be
On iOS, if you want to send an email, you simply use MFMailComposeViewController. Give it your subject, recipients, body, even attachments, then fire and forget. On Mac, you’ve got to use the Scripting Bridge or some weird AppleScript hack (which is basically the same thing).
Now I suppose this one is understandable because of the number of different mail clients that exist on Mac OS X and the impossibility of supporting them all. But sending an email is so much more convenient on iOS than on Mac. They could at least provide a default API for Mail.
Custom windows must be done manually.
Have you seen the custom window toolbar that the Mac App Store uses? Or Twitter/Reeder/Sparrow? Looks nice, eh? Turns out that if you want one, you’ve pretty much got to draw it yourself, and then manage all of your toolbar buttons yourself. And then manage the resizing of the window yourself. And then manage the dragging of the window yourself. And then manage the positioning of the traffic lights yourself. This will help you get started, but it’s not quite perfect.
Popovers don’t exist prior to Lion
We’ve found the popover paradigm to be quite useful, but unfortunately it doesn’t exist natively until you get to Lion. Developer seem to make do with Matt Gemmell’s MAAttachedWindow, but it still requires a bunch of extra work.
Animations are tricky.
Dear god, the hoops we had to jump through to implement a simple swipe animation that we would have gotten for free on iOS. The problem is that unlike iOS, NSViews aren’t layer backed by default, and you need the layer backing to do reasonably performant animation.
No problem, just turn on layer backing for all views in your app, right? No, because layer backed views can’t do subpixel antialiasing, which makes all your text ugly as death.
No, as it turns out, the solution is to capture a temporary bitmap of the view you want to animate, stuff that inside a layer backed view, animate that instead, and then remove it from the screen when the animation completes. Argh!!!
Navigation stacks don’t exist.
The entire UINavigationController stack doesn’t exist on Mac. I suppose this makes sense because most Mac apps don’t work like that, but we wanted to design our app this way to avoid excess clutter on the screen (e.g. the dreaded 3-pane layout where the main content is barely visible because of all the other panes in the way).
Of course, this meant handling the navigation stack manually. I was also surprised that wizards must be done manually considering how frequently they appear in Mac apps (thanks CIMGF!)
Fewer useful customization points
In general you’re doing a lot more random subclassing when customizing AppKit. iOS views generally have great hook points (in the form of delegate methods) that allow you to customize your view behaviors without having to subclass everything. In AppKit, we found ourselves having to create all sorts of random subclasses to implement behaviors that probably should have been available via delegates. For example, we’ve got a trivial NSSplitView subclass that exists solely to change the split divider color, and a trivial NSTableView subclass that exists solely to change the highlight selection color, a trivial NSTextField subclass that exists to fire a delegate when it becomes the first responder, a bunch of custom button cell subclasses to implement hover highlights and hover tooltips, and a NSImageCell subclass that exists to draw a stretchable image (UIImage’s very useful stretchableImageWithLeftCapWidth:topCapHeight doesn’t exist).
Summary
Yikes, that was a long list wasn’t it? Suffice to say that building a full featured Mac app is much more challenging than its iOS counterpart, especially if you want to design a ‘modern’ looking UI.
You have to hand roll a bunch of stuff you probably didn’t expect and it’s generally more work than it should be.
Apps like Twitter/Reeder/Sparrow are doing a LOT of custom drawing to look the way they do, so if that’s the effect you’re going for, be prepared!
Was it worth the effort?
It wasn’t easy, but I would definitely say yes. At this point our Mac app sales are dominating our iPhone/iPad sales. Paprika is currently the No 2 paid app and No 1 Grossing app in the Lifestyle category in the Mac App Store, whereas we are floating around 20 on iPad and 30 on iPhone. (The Lifestyle category on iPhone and iPad is a lot more competitive now compared to last year when we launched Paprika, since all sorts of magazines and food companies have entered with their own apps.)
That said, it’ll probably be a while before we attempt to tackle another Mac app.
-
Carolynzick
-
http://www.facebook.com/profile.php?id=1298256819 Caroline Ashwood Walsh
-
Brian Whisler
-
Sigats2