Two things happened on Thursday that made it obvious to me what I should write about this week. Mountain Lion was announced, and my first Mac App was approved for the Mac App Store.
Even though iDevBlogADay is about iOS programming, more and more of us are moving from iOS to the Mac. With the announcement that GameCenter will be coming to OS X, I’m guessing that more iOS developers might be thinking about coding for the Mac now than might have been last week.
So today I’m going to talk about my experience in getting my first App on the Mac App Store and specifically the differences in the approval process between the Mac App Store and the iOS App Store.
I wasn’t nearly as prepared as I thought
I first submitted my Mac App on October 17th, so it took me a day short of 4 months to get my App approved. That’s at least 3 times longer than I ever took to get any of my iOS apps approved. Partly because the MAS requirements are different, and part because I wasn’t paying enough attention. I learned a lot in the process, and I thought I would try to help other people avoid (at least some of) the mistakes I made. Hopefully this will help you avoid wasting both your time and the review team’s time.
Note: I’m only going to focus on the general interest stuff in this post. Since I wrote a developer tool whose primary purpose is to make things happen on a remote iOS device, it doesn’t fit into the expected behavior of a “normal” OS X App. So I’m going to ignore the issues I had that are specific to the type of App I wrote.
You can’t require 3rd-party Apps to be installed
My App uses Dropbox and Boxcar to “push” an iOS App build to all my test devices wirelessly. And my first rejection was because my app required third party products to be installed. That was a surprise to me, because I have used several iOS apps that require 3rd party services (how many iOS Apps have you seen that do nothing until you log in with Facebook Connect? – I’ve seen several).
So this required me to sit back and do a redesign (or sorts). Since I had built the entire App intending to rely on those services, I didn’t know what to do. So I added a new preference so that, instead of Dropbox and push notifications, you could use local Web Sharing and send an email to your device with the link to click to install your test App. It’s not nearly as useful as the Dropbox/Boxcar method, and I feel there should be a different solution, but I haven’t thought of a better way that could work inside the sandbox restrictions.
But the main thing is, my App is functional (even if in limited way) if you don’t have Dropbox and Boxcar, so it got me past that hurdle. (Personal aside: I use both Dropbox and Boxcar on an almost daily basis and if you don’t, and you have both a Mac and either an iPhone or iPad, I think you’re missing out on two of the most useful tools available to you).
The first rule of Sandbox is you don’t talk about Sandbox
Another rejection was because I had a description that said something like “To comply with Sandbox restrictions, if you use Dropbox, it must be installed in $HOME/Dropbox”. It seemed a reasonable thing to me at the time. In order to write to the Dropbox folder (with sandboxing enabled), I have to put the Dropbox folder path in the temporary exceptions part of my entitlements file. I understand why Apple doesn’t want us doing that – it means that, if they change their policy, then our descriptions would become inaccurate. Also, most users don’t understand such things, but hopefully all of mine do, since all this App’s users are iOS developers.
I had read the review guidelines, but I would not have thought this wouldn’t be okay. The rule that this violates is: “3.3 Apps with descriptions not relevant to the application content and functionality will be rejected”. So keep in mind that talking about internal Apple procedures might be considered “not relevant” and earn you a rejection.
Test on Macs with different configurations
I was also rejected because my App crashed if run on a Mac that didn’t have Xcode installed. The reason for that is beyond the scope of this post (and wouldn’t be relevant for most Apps), but it had never occurred to me to try to test my code on a machine without Xcode installed (all my Macs have Xcode – why wouldn’t they – it’s what I do all day). I uninstalled Xcode from one of my machines, and sure enough, I got the crash.
It was easy enough to fix the crash, but the important thing I learned was: I needed to have tested on configurations that were more generic than what I normally use.
They are serious about keeping the user informed
I was rejected for not including a progress bar.
Well, not explicitly – but there was a point while using my App when there was a pause, and no feedback was being provided to the user. Stuff was happening in the background, and I wasn’t blocking the UI thread or anything, but I wasn’t telling the user what the App was doing. It didn’t seem (to me at least) that it lasted that long, but they rejected it.
So I put a progress bar on it and, you know what, it greatly improved the user experience. I’m really glad I got rejected for that, because I like it much better now.
They are serious about User Experience
I also got rejected because I didn’t specify a minimum window size. Or at least, adding a minimum window size fixed the problem.
The rejection said that the user could resize the window so small that the app could no longer be used. They were correct – that window exists to be a drag-and-drop target, and if you make the window small enough, then you can’t drop your .app bundle onto it anymore. My initial reaction was “well, the user shouldn’t do that and still expect it to work”. But I was wrong.
The bottom line is that I hadn’t even considered what I wanted the App to do if the user resized the window – windows don’t get resized on mobile devices, so it hadn’t entered my thought process. After that rejection, I thought about what I wanted the App to do when a resize happened. And it’s a much better App for having thought that through.
So, in conclusion: It’s as much about design as code
I would say that, more than anything else, I learned that the difference between the User Experience on OS X and iOS is at least as great, if not greater, than the difference between writing code for OS X and iOS. When I was reading books and teaching myself about programming for the Mac, I tended to focus on the framework changes (e.g. NSView vs. UIView) and the new elements (NSMenu, NSDraggingDestination, NSTask, etc) and I mostly skimmed the design parts. That was a mistake.
Over the months, I got very frustrated with the review process. There were times I thought that I was never going to get the App approved. But even though people kept telling me that I should just withdraw it and sell it outside the App Store, I’m glad I didn’t give up. It’s a much better App than it would have been if I hadn’t done what it took to get it approved for the store.
WARNING: Blatant Self Promotion Ahead
Don’t say I didn’t warn you.
If you’ve read this far, and are an iOS developer, you might be interested in my new App, AppDropOTA. Which you can get for about the cost of the proverbial cup of Starbucks coffee here (App Store link).
After installing it, you configure it with your Boxcar email address and copy and paste a Dropbox Public folder URL into the Preferences panel. (Also, if you don’t have it, you should go install the Boxcar App and log in to it with your email address).
Now use Xcode to build an iOS App for the device, grab the .app bundle and drop it onto the AppDropOTA window. Your .app bundle will be turned into an .ipa file and placed into your Dropbox Public folder so it’s accessible via HTTP. Then you’ll get a push notification on the Boxcar app on your device(s). Launch Boxcar (via Notification Center is the quickest way), open the notification, and tap the “View Original” link on the item detail screen. You’ll get an alert pop-up prompting you to install your App. Click “Install” and you’re done.
When I’m testing, I do this several times a day. I have 9 iOS devices I test with: an iPad 2, an iPhone 4S and an iPhone 4 running 5.0, an iPod Touch running 5.1 beta, an iPad 1, an iPhone 4 and an iPod touch running 4.3 and an iPhone 3G and a iPod Touch running 4.2. (Soon, I hope to convince my customers to drop 4.x support, but haven’t, yet). I learned the hard way that I needed to test on different hardware when I shipped an App update for a customer that immediately crashed on all armv6 devices because of this Xcode bug. I wrote AppDropOTA to speed up the process of getting Apps on all my test devices.
Some people use TestFlight for this. For me, TestFlight is too public to use for every test build. I use TestFlight for builds I’m sharing with my customers, but I never push something to TestFlight until I’ve run it on my own device first. AppDropOTA is what I use for that (and it’s faster that TestFlight, too).