Adapting and sharing code for iOS and OS X

So now that I have started writing OS X application for the mac and there are obviously a lot of similarity with iOS I decided to write some companion app on OS X for my new project TennisStats.

Most of the code is quite abstracted between the UI and the data logic behind, which helped a lot, but I embarked upon sharing my main plotting library inherited from ConnectStats. What will it take to share that library between iOS and OS X?

Compiler Macros

The first thing was to figure out the right macros to predicate the code between the two OS. TARGET_OS_IPHONE and TARGET_OS_MAC are define and the one to use here. Though one thing I quickly learned was the really I should use TARGET_OS_IPHONE as TARGET_OS_MAC is defined both for OS X and iOS.

The key library can then be imported with #if. Note that CoreGraphics seems to be the same across both.

#if TARGET_OS_IPHONE
#import 
#import 
#else
#import 
#import 
#endif
#import 

UI and NS prefix

A lot of the classes prefixed by UI provided by apple on iOS have equivalent named with NS. Was it going to be as easy as using one for the other?

Brief look at the class reference showed a lot of similarity and iOS was derived from OS X, so why not… So I started to add the following typedefs

#if TARGET_OS_IPHONE
#define RZColor UIColor
#define RZFont UIFont
#define RZImage UIImage
#define RZView UIView
#define RZBezierPath UIBezierPath
#else
#define RZColor NSColor
#define RZFont NSFont
#define RZImage NSImage
#define RZView NSView
#define RZBezierPath NSBezierPath
#endif

And the replace each instance of UIColor, UIView, etc with the equivalent version prefixed with RZ. Most of it compiled! NSBezierPath has the most differences, and I had to write an extension to provide the following functions

extern CGContextRef UIGraphicsGetCurrentContext();
extern NSString * NSStringFromCGPoint(CGPoint point);

@interface NSBezierPath (QuartzHelper)

+(NSBezierPath*)bezierPathWithBezierPath:(NSBezierPath*)other;
+ (NSBezierPath *)bezierPathWithCGPath:(CGPathRef)CGPath;
-(CGPathRef)CGPath;
-(void)addLineToPoint:(CGPoint)point;

@end

So now it compiles… Will it just work and is that easy?

Ooops

Oops… A new issue to worry about, the coordinates in OS X and iOS have their Y axis inverted… Luckily I had quite nicely abstracted the CGPoint calculation in a Geometry class… So a key target predicate

-(CGPoint)pointForX:(CGFloat)x andY:(CGFloat)y{
    
#if TARGET_OS_IPHONE
    return CGPointMake( (   _graphDataRect.origin.x + (x-_dataXYRect.origin.x)* _horizontalScaling),
                       _graphDataRect.origin.y+_graphDataRect.size.height - (y - _dataXYRect.origin.y) * _verticalScaling);
#else
    return CGPointMake( (   _graphDataRect.origin.x + (x-_dataXYRect.origin.x)* _horizontalScaling),
                       _graphDataRect.origin.y+ (y-_dataXYRect.origin.y) * _verticalScaling);
#endif
}

And a few more around the title and axis location, et voila!

Better

In Summary

It was much easier than I expected. And definitely makes the thought of building an OS X companion app very realistic…

Find the iOS Simulator Document Directory

I really enjoy working with Xcode 6, but it has been quite annoying that the iOS Simulator changes the name of the directory it uses as data container each time you start it. There are a few manual method to find and get to the document container I had been using. I will describe them here. Because my apps tends to use a lot of files in data that I need to check while debugging, I also created a little tool that wraps the manual methods into a very useful (at least to me) and easy workflow. you can download the app here.

Manual logging

The easiest way is to add logging of the directory location on startup. I typically make it conditional to being in the simulator as to no clutter the log in a device. Here is what I typically add to the applicationDidFinishLaunching function.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if TARGET_IPHONE_SIMULATOR
	NSLog(@"Simulator : %@", NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES)[0]);
#endif
	//...
}

I then copy from the output of the console the directory and paste it into finder, terminal, emacs or your tool of choice

Simple. Efficient. Tedious

Needle in a haystack

A different approach would be to save a small file in the document directory on start up which then allows you to find that simulator without having to start the app and refer to the console. It would look something like this:

#if TARGET_IPHONE_SIMULATOR
    NSString * __rzSimNeedlePath = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES)[0];
    [[NSData data] writeToFile:[NSString stringWithFormat:@"%@/.needle", __rzSimNeedlePath] atomically:YES];
#endif

If as above you save a file with the app identifier in the name as above, you can then later generically find that directory using a find command and then wrap that into your favourite scripting language

find ~/ -name .needle -print

LastLaunchServicesMap.plist

You can also find a file in the Library of the device directory of a simulator that contains the path of the last container. That file is under the path Library/MobileInstallation/LastLaunchServicesMap.plist. You can then print it using plutil -p LastLaunchServicesMap.plist, the key User contains information about each application in the simulator. Somehow it still seems to be missing some apps, but contains most of them.

Simulator Data Finder

To make my life easier, I built this app that leverages the last two methods to make it easy for you to find your simulators and files. The app can present you with a list of simulators and app as below. It then has a easy access button to finder, copy to clipboard or terminal for the path. It will try to find the directory using the LastLaunchServicesMap.plist file and if it still doesn’t find it, you can download this header file and add the following macro call in your applicationDidFinishLaunching,

You can download the app here. You can read here why I was unable to publish this app in the apple App Store which would have been more convenient.

One added bonus to the app is that it organises for easy access container you’d have downloaded from a device for a given app. It matches them by bundle identifier and currently looks for them in the download directory.

Upcoming Features for connectstats

Development on ConnectStats has been very slow recently. Mostly because I have started a new app related to Tennis Stats, which I’ll likely release soon. I have a few upcoming features for ConnectStats I still need to wrap up and a few bugs reports to investigate but didn’t get much time to focus unfortunately.

The main feature I have in the back burner for a while is the ability to compute best rolling heart rate or speed profile for current month or year and show that in a summary page. It isn’t working well at the moment, but the summary page is quite useful, so I may just release that alone. The stats tab now by default shows a summary page with key graphs, and of course the old pages can still be accessed.

The other small feature is I finally figured out how to optimise the screen for iPhone 6 and iPhone 6+, so that will be included too. Meanwhile, here is a preview of what the summary page looks like on an iPhone 6 resolution:

SummaryPage

The path to rejection in the apple App Store

I have been developing apps on iOS for quite a while now as a side hobby. The latest Xcode has an ever changing directory for the Xcode simulator, which has been a bit painful to work around, despite a few tricks. So I decided it was a good opportunity for me to get into OS X application development and build a little tool that would let me organise and access files on the simulator for each app work on. I had been always writing my user interfaces fully programmatically so I also decided I would take this opportunity to explore interface builder and storyboard.

The first impression of OS X was that it’s actually not that different, some classes have different names and APIs but it’s remarkably similar. I felt right at home using NSTable. I got the basic of the app working very quickly and it instantaneously proved quite useful to my app development workflow. So much so that decided to share it on the AppStore as a free utility for other developers.

So on I go, paid my $99 to apple to become a Mac registered developer, as until know I only was an ios developer.

Sandboxing

First hurdle was that I had to sandbox the application. This meant that access to the terminal via AppleScript stopped working, but most significantly it wasn’t possible to access the simulator directory as it was outside the sandbox. I used the terminal as I wanted the app to let you pop up a finder window or a terminal window on the directory of a selected app.

Not to worry, according to the sandbox guideline you can access any directory as long as the user grant access by showing intent: all I had to do was to bring up a open dialog window on the directory where the simulator reside and once the user pressed ok, the app had access.

For the terminal, once you figure the syntax you could explicitly ask for access to terminal in the entitlement file. Et voila, all the functionality was working again in a sandboxed app.

There was though one major issue. Every time you pop up the open dialog, it would pop up a new duplicate window. It was really annoying and drove me nuts for a while. Without sandboxing it was all working fine, as soon as you turned on sandboxing that duplicate window kept coming up. I searched in the development forums, documentation, etc. I didn’t find any solution or mention of that problem. So I filed a bug report (easy to reproduce the problem with a very simple project) and used one of my support tickets (you get two as a registered developer) to ask an apple engineer for help.

Almost two weeks later I got the answer from apple. It is a bug on apple side, to solve it I should not use storyboard… Great. So I refactored all my code to use an xib file instead of storyboard and now it worked!

First Submission

Feeling all set to go, I purchased an icon on shutterstock, quickly added some help and documentation to the app and submitted it to the Apple Store for review. One week later, I got my first rejection. I so far never received a rejection for my iOS app, so it was quite disappointing. The feedback was that I used entitlement that I shouldnt.

I figured this must have been the access to terminal. Even though it seemed to use the standard method to request entitlement, I had always been a bit worried in the back of my mind that accessing terminal could be a security issue because it would let you execute potentially damaging commands.

It made sense and I removed that functionality and resubmitted the app. One week later another rejection with the same reason. You can’t be serious! Actually it was: silly me I had resubmitted the app without selecting the new binary, so they reviewed the same app I had submitted the first time. Another week wasted.

Final Rejection

I carefully selected the updated binary and resubmitted the app.

Almost two weeks later the app goes into review. There are typically two stages in a review: waiting for review, that in my previous experience takes a few days to a week, then it becomes in review, which takes a few hours to a day or two. Nice, I felt hopefully it will go through soon…

Not so fast. A few days pass. Then a week. Still in review. I almost forgot about it when after over three weeks, I start to wonder. Did something happen? I did some research on the Internet, app review times gives some average days to review of 8 or 9 days. I am way beyond… After a month of being in review I write to apple asking if there is any information they need from me to review the app. The answer comes quickly: no information needed, they are still reviewing the app and just had to be patient.

After more than once month I just received the final rejection. The app is rejected because “I am modifying user file in a way that is not publicly documented”. I am not modifying any file in the app! But it then hits me: apple just does not want me to write this app.

I write a last appeal to the reviewer: I do not modifying any file in my app, only bring up a finder window on a directory after the user has granted access and showed intent by clicking Authorize on an open dialog box. The final answer from the reviewer: we just don’t want app to access simulator files.

Here we go. Hard to not feel a bit of frustration and disappointment, almost three months after starting the app. I still believe this app is really useful and became an important part of my debugging workflow. So I put a bit of wrap around and made it available to download via this website and hope others will find it useful as well.