Programming

I’ve decided to open source PicSlide, which was one of my first iOS games. It isn’t selling enough to make it worthwhile updating for the iPhone 5. When I looked at the code I didn’t find it too embarrassing and it didn’t make me want to vomit, despite being written in 2009, so I decided to share it. Some pieces like scaling & slicing images and doing Quartz drawing may be helpful as sample code.

The source code is now available on Github at https://github.com/mike3k/picslide

I’ve submitted a minor update to Sugar Rush which changes the FaceBook SDK to use the latest API so sharing will continue to work. It also adds support for built-in Twitter accounts in iOS 5 or later and updates the Kiip SDK to the latest version.

Sugar Rush Free gets a much bigger update. In addition to those SDK changes, I’ve renamed it to Sugar Rush Lite and added in-app purchase to get rid of the ads & height limit. You can also buy Donuts, which will make it functionally equivalent to Sugar Rush Pro. Both Sugar Rush Lite & Sugar Rush Pro now share the same Game Center leader board & achievements.

When I submitted an update to Detective, I discovered a few tricky things related to sandboxing and embedded helper apps.

In order to support ‘start at login’ in a sandboxed app, you need to embed a helper app that launches the main app (the entire process is described here). What I didn’t realize is that the helper app also has to be signed, or it will fail to let you start it at login. However, when you sign the helper app, it will include its own embedded provisioning profile, so when you try to submit your app, it will be rejected with the following message:

Invalid Provisioning Profile Location – The provisioning profile for your Mac OS X app must be located in the Contents directory of the main app bundle. A provisioning profile is optional, but you cannot submit more than one.

One of the suggestions in Apple’s developer forum is to remove the embedded profile from the helper app. Note that deleting the embedded profile doesn’t affect the actual code signing. After some experimentation, I found that the easiest way to do it is to add a Run Script build phase to the main application that deletes the profile from the helper app:

#!/bin/sh
rm ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Library/LoginItems/DetectiveLoginHelper.app/Contents/embedded.provisionprofile

After doing this, I was able to submit the app successfully.

Apple will soon require all Mac apps submitted to the app store to be sandboxed for heightened security, which means it needs to request permission for doing even basic things like accessing files or connecting to the internet. A lot of things like disk burning aren’t allowed at all in the sandbox, and some things like sending AppleEvents require temporary exemptions which will be phased out.

One common task that’s complicated by the sandbox is adding a login item. A good tutorial on creating login items is available at delite studio, although some changes to the code are needed. That code follows Apple’s earlier guideline of using LSRegisterURL to register your helper app. However, I found that it always fails with error -10819. According to an Apple engineer in Apple’s developer forum, you should not call LSRegisterURL in a sandboxed app.

Note that your application can’t add itself as a login item. You need to write a simple helper app that launches your main app and it must be included in the main application bundle in the relative path /Contents/Library/LoginItems/.

The method for adding & removing a login item turned out to be very simple:

- (void)addLoginItem {
    NSString *ref = @"com.madebynotion.myLoginHelper";
	if (!SMLoginItemSetEnabled((CFStringRef)ref, true)) {
		NSLog(@"SMLoginItemSetEnabled failed.");
	}
}

- (void)removeLoginItem {
    NSString *ref = @"com.madebynotion.myLoginHelper";
	if (!SMLoginItemSetEnabled((CFStringRef)ref, false)) {
		NSLog(@"SMLoginItemSetEnabled failed.");
	}
}

If you need to find out whether your login item is enabled, here’s a way to do it:

-(BOOL)appIsPresentInLoginItems
{
    NSString *bundleID = @"com.madebynotion.myLoginHelper";
    NSArray * jobDicts = nil;
    jobDicts = (NSArray *)SMCopyAllJobDictionaries( kSMDomainUserLaunchd );
    // Note: Sandbox issue when using SMJobCopyDictionary()
    
    if ( (jobDicts != nil) && [jobDicts count] > 0 ) {
        
        BOOL bOnDemand = NO;
        
        for ( NSDictionary * job in jobDicts ) {
            
            if ( [bundleID isEqualToString:[job objectForKey:@"Label"]] ) {
                bOnDemand = [[job objectForKey:@"OnDemand"] boolValue];
                break;
            } 
        }
        
        CFRelease((CFDictionaryRef)jobDicts); jobDicts = nil;
        return bOnDemand;
        
    } 
    return NO;
}

Although this method works and seems to be the preferred way to do it in the sandbox, it’s less optimal than the old non-sandbox method, since your login item won’t appear in the users & groups preference panel’s login items list and can only be turned on & off from your own application.

I’ve been able to test my apps in iOS 5 and the only one that has an issue is Removr. Sugar Rush is 100% compatible with iOS 5 and doesn’t require an update.

Removr immediately returns to the menu when you tap play or try to go to a level. I’ve already fixed it and will submit the update after some further testing as soon as Apple accepts iOS 5 application updates.

There seems to be a change in SQLite’s behavior under iOS 5 which affected Removr. I’m storing the level maps as a SQLite database in the application bundle. In the current version, I’m opening the database as follows:


int err = sqlite3_open([self.dbpath UTF8String], &db);

Under iOS 5, that fails unless readonly mode is specified when opening the database. The simple fix was merely to specify read only mode as follows:


int err = sqlite3_open_v2([self.dbpath UTF8String], &db,SQLITE_OPEN_READONLY,NULL);

I will release a Removr beta in the next few days to anyone whose device ID I have (I can’t add any new devices until July 16).

Our early sales for Sugar Rush were very low, despite a big PR push.  We had a problem on the first day when the screen shots disappeared for a few hours after I tried to update the description & screen shots. Second day sales were even worse.

However, I noticed something very interesting: the number of users in Game Center and the number of users reported by Flurry Analytics were at least 3 times the total number of sales reported in iTunes Connect. At first I thought the iTunes reports were delayed, but a google search revealed that there are lots of pirated copies available. If the numbers are accurate, this means there are at least 3 or 4 times as many pirated downloads as we had legal sales.

I’m amazed and disappointed that an app can be that widely pirated after only two days of sales, especially when the legal sales were lackluster.

As a result I’m very unlikely to develop any future iOS apps, since it looks like I can’t make a living on app sales.

UPDATE: according to a source, the illegal copy got 2000 downloads, which is a lot more than 4x the number of legal downloads.

Although Xcode is all you really need to get started with iOS development, there are a lot of useful tools that can make your life a lot easier.

When you’re writing code, Accessorizer can eliminate a lot of the drudgery. You usually find yourself creating lots of properties, which involves declaring an instance variable, adding a @property declaration, and @synthesize in your implementation file. With Accessorizer, you simply select the instance variables, hit a hotkey, and copy the generated code into your source. For example, if you have the following ivars:

NSString *title;
UIView *aView;
NSInteger count;

Accessorizer will generate the property declarations & implementations, which you can customize. In the simplest case, you’ll get this:

@property (nonatomic, copy) NSString *title;
@property (nonatomic, retain) IBOutlet UIView *aView;
@property (nonatomic, assign) NSInteger count;

@synthesize title;
@synthesize aView;
@synthesize count;


You can also have it generate init & dealloc methods and accessor methods.

For source control, you’ll probably want something more advanced than Xcode’s built in source control, especially if you’re still using Xcode 3.2.x. Even with Xcode 4, it only tracks files included in the project, not related files such as artwork & documentation not included in the project.

For Subversion, I like Versions. After looking at every Git GUI client, the only one I found that I like is Tower. For a file comparison & merge utility more advanced than Apple’s FileMerge, I like Changes. It gives a lot of display options and is also really nice for comparing directories.

If you’re using Cocos2D, you’ll need a few utilities to generate sprite sheets, textures, and particle effects,

One tool that’s absolutely essential is a sprite sheet generator such as TexturePacker. It takes a collection of single images and packs them into a single image file with the most efficient arrangement along with a plist that tells how to access each piece. TexturePacker is available in several free & paid versions.

In many apps, you’ll use some particle effects such as smoke, fire, and explosions. Particle Designer makes creating them fun & relatively easy with a large online collection of shared emitters. The plist file it generates can be used with a single line of code.

If you’re using a tile map, Tiled is a nice, free tile map editor.

If your app uses the accelerometer, you’ll need iSimulate to test it in the simulator. iSimulator consists of two components: an iPhone app and a library you include in your simulator builds. When you run the iSimulate app, all movements & multi-touch events will be sent to your app.

Finally, you’ll need to create a nice demo video for your app. Sound Stage is a great way to record videos from the simulator. It can record either the app content only, the app running inside the iPhone, or a custom background. You can also use iSimulate with it.

I’m working on a new game that depends on the accelerometer, which makes testing in the simulator difficult. I asked on Twitter about a hack to use the accelerometer in the simulator and was pointed to iSimulate.

iSimulate is a brilliant application that runs on your iPhone, along with a library that needs to be linked into simulator builds of your application. The iSimulate application connects to your computer over your wireless network and lets you send accelerometer, compass, and multi-touch events to the iSimulate-enabled application running in the simulator.

After you finish developing your app, you can then use iSimulate to record video trailers & demos of your app.

In a new app I’m working on, I created a series of lightweight cocos2d sprite subclasses which simply call the superclass init with some parameters. As soon as I called it I ended up crashing with a weird looking stack trace showing lots of nested calls to init methods.

Screen shot 2011-03-06 at 2.18.44 PM.png

My init methods were pretty simple:


@implementation Gummybear

-(id)init {
    return [super initWithName: @"gummybear.png"];
}

@end

My superclass’s init method used CCSprite’s initWithSpriteFrameName: method.


- (id)initWithName: (NSString*)name
{
    self = [super initWithSpriteFrameName:name];
    if (nil != self) {
		// do some more initialization here
    }
    return self;
}

It ultimately ends up calling initWithTexture:, which is where the problem lies. It turns out initWithTexture: is calling [self init]. Guess which init method was getting called?


-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect
{
	NSAssert(texture!=nil, @"Invalid texture for sprite");
	// IMPORTANT: [self init] and not [super init];
	if( (self = [self init]) )
	{
		[self setTexture:texture];
		[self setTextureRect:rect];
	}
	return self;
}

The fix was to simply change the name of my init method to something else.