BuySellAds.com

My book Barcodes with iOS 7 is nearing completion. Buy it now to get early access!
Our DNA is written in Objective-C
Jump

Category Archive for ‘Recipes’ rss

Unicode, Schmunicode!

You are listening to user feedback, especially those in Italy. You solve all their problems with a new version, in my case LuckyWheel 1.0.3. You polish it, test it (you think) and submit it to Apple for review. After a week you get this message back:

Your applications, LuckyWheel and LuckWheel Lite, cannot be posted to the App Store at this time because they do not achieve the core functionality described in your marketing materials, or release notes.  Applications must adhere to the iPhone Human Interface Guidelines as outlined in iPhone SDK Agreement section 3.3.5.

The release notes for both applications state, “Italian UI and Instructions added”.  However, in our review, when we put the device into Italian language mode and launched the applications, the application UI was still in English.  Only the instructions were changed to Italian.  See attached screenshots.

In order for your applications to be reconsidered for the App Store, please resolve this issue and upload your new binaries to iTunes Connect.

That’s a very long way to say: “Hey buddy, your Italian is English!”

When I got this message I was stumped. I though I had tested it. Thieves! Who has stolen my Italian UI?! But then I remembered something I had found out some months ago.

Read more

Anything goes … into NSArray

When switching to or beginning with Objective C you might be tempted to try to use the old c-style arrays, but that’s better left to the hard core C-enthusiasts. For programming Cocoa Touch we always use the NSArray class because of the additional intelligence it provides for us, not to mention integration with memory management.

The first thing I ever added into NSArray was string objects. And so will probably everybody who starts with Objective C.

NSString *someText = @"Static Text";  // static allocation
NSString *someMoreText = [[NSString alloc] initWithString:@"More Static Text"]; // manually allocated
NSArray *myArray = [NSArray arrayWithObjects:someText, someMoreText, nil];  // note the nil
[someMoreText release];  // don't forget to release
 
NSString *retrievedText = [myArray objectAtIndex:1]; // first index = 0
NSLog(retrievedText);

How about numbers? Generally you can only add instances of objects into NSArray. But luckily Apple has created the NSNumber class which provides a container object for any kind of number, i.e. int, float or even BOOL.

int i=123;
float f=5.0;
 
NSNumber *num_i = [NSNumber numberWithInt:i];
NSNumber *num_f = [NSNumber numberWithFloat:f];
NSNumber *num_b = [NSNumber numberWithBool:YES];
 
NSArray *myArray = [NSArray arrayWithObjects:num_i, num_f, num_b, nil];

With the methods above you are able to add strings and numbers into arrays. There is yet another wrapper class that allows to put even more complex data types and structs into arrays: NSValue. Most usefully are the UIKit additions to NSValue which give you the possibility of packaging CGRect, CGPoint, CGAffineTransform or CGSize structs into objects. And those are just as easy to put into an array.

CGRect aRect = CGRectMake(0, 0, 100.0, 100.0);
CGSize aSize = CGSizeMake(10.0, 20.0);
 
NSValue *val_rect = [NSValue valueWithCGRect:aRect];
NSValue *val_size = [NSValue valueWithCGSize:aSize];
 
NSArray *myArray = [NSArray arrayWithObjects:val_rect, val_size, nil];

Soon you will find it odd that NSArray does not have any method to add and remove objects. The reason for this is that most standard objects are non-changable (aka “immutable”) as such. To gain such modification features you have to use the mutable cousin NSMutableArray. This gives you methods like addObject or removeObject.

NSMutableArray *myArray = [[NSMutableArray alloc] init];
[myArray addObject:@"first string"];
[myArray addObject:@"second string"];
[myArray addObject:@"third string"];
 
[myArray removeObjectAtIndex:1];
 
NSLog([myArray description]);  // a quick way to show contents
 
[myArray release];

NSArrays are the meat and bones of most Objective-C apps. Anybody trying to master this language has no way around them.

Getting Standard Paths

These are the standard methods of getting the app directory and the documents directory. Remember that on the iPhone the app as well as the documents directory are “sandboxed” i.e. each app has it’s own two directories.

Here’s how you can get the path for files in your app bundle/directory:

// a regular file
NSString *plistPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"File.plist"];
 
// this works also for localized files, gets the best matching localized variant
NSBundle *thisBundle = [NSBundle bundleForClass:[self class]]; // alternate method to get bundle
NSString *path = [thisBundle pathForResource:@"File" ofType:@"plist"];

One common mistake is to pass a file name including extension in the first parameter  of the pathForResource method. This won’t work, pass only the name without extension as first parameter and only the extension as second parameter.

And to get something from the documents directory you can do it like this:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@"File.plist"];

You’ll find yourself copy/pasting these lines all the time basically for every file that you are reading or writing.

Drawing Text in Graphics Contexts

Sometimes you might need to draw text instead of just using a comfortable UILabel. For example if you want to show it a a specific angle. This takes a lot of code, but once you understand it, it’s just copy and paste.

- (void)drawRect:(CGRect)rect {
	CGContextRef ctx = UIGraphicsGetCurrentContext();
 
	CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);   // inside red
	CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1);  // outline green
	double text_angle = -M_PI/4.0;  // 45 Degrees counterclockwise
 
	CGAffineTransform xform = CGAffineTransformMake(
		cos(text_angle),  sin(text_angle),
		sin(text_angle), -cos(text_angle),
		0.0,  0.0);
 
	CGContextSetTextMatrix(ctx, xform);
	CGContextSelectFont(ctx, "Helvetica", 20.f, kCGEncodingMacRoman);
	CGContextSetTextDrawingMode(ctx, kCGTextFillStroke);
	CGContextShowTextAtPoint(ctx, 100, 100, "Test Text", 9);
}

Of special importance is the application of a tranformation matrix via CGContextSetTextMatrix. Without it, your text will be drawn upside down. This is a feature, not a bug, because internally the iPhone still wishes that (0,0) is in the lower lefthand corner, but for most UIView-releated uses this is the “right way around”, i.e. (0,0) in the upper lefthand corner.

Angle of a Vector

When dealing with a multi-touch rotation I was searching for a simple method to calculate the angle of two fingers. Well, actually more precisely the angle between the vector from one finger to the second finger and the horizontal X Axis.

While I was still searching my friend Christian Pfandler came up with this function:

- (CGFloat)angleFromPoints:(CGPoint)pos1 otherPoint:(CGPoint)pos2
{
    CGPoint vector = CGPointMake(pos1.x-pos2.x, pos1.y - pos2.y);
    double angleCalc;
    if (vector.y < 0)
    {
        // upper Half
        angleCalc = atan2(-vector.y,vector.x);
    }
    else
    {
        angleCalc = atan2(vector.y,-vector.x)+M_PI;
    }
 
    return angleCalc;
}

The regular atan(x) function seem to have a limitation by only returning values between 0 and +/- PI. Because of this there is also the atan2(y,x) function which is orders of magnitude more useful. To get a continuos angle for the full circle we have to treat the upper half and the lower half seperately though. I found this here.

I don’t quite understand it, but it seems to work. :-) Please keep in mind though that like all angles in Cocoa Touch this is in radians as well. So not 0…360, but 0…2*PI. If you know an even simpler method, please let me know.

NSArray Sorting

If you need the contents of an NSArray in a different order than they where entered into the array you have several options available. By far the simplest is to use descriptions if you have a standard data type you wish to sort by. You first set up an array with the names of fields to sort by. Then you sort.

// first you need to set up the descriptor
 
NSSortDescriptor *firstDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"firstField"
							 ascending:YES] autorelease];
NSSortDescriptor *secondDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"secondField"
							ascending:YES] autorelease];
 
NSArray *sortDescriptors = [NSArray arrayWithObjects:firstDescriptor, secondDescriptor, nil];
NSArray *unsortedObjects = [NSArray arrayWithObjects:oneObject, secondObject, nil];
 
// new and shiny sorted array
NSArray *sortedObjects = [unsortedObjects sortedArrayUsingDescriptors:sortDescriptors];

A little more involved but way more powerful is the method of using a selector. Here you add a function to your special class that you are keeping in the aray which takes care of deciding which of two object comes first: self or the other one.

Note that the sort method needs to be part of the class itself. You can make your sorting logic as complex as you like to using multiple sort methods. Cocoa Touch calls this method every time it needs to know the order of two objects. Having access to all the other object’s properties you can have your function return NSOrderedAscending, NSOrderedDescending or NSOrderedSame depending on what order you determine to be correct.

//  MyOwnClass sort function
- (NSComparisonResult)compareByTime:(MyOwnClass *)otherObject
{
	NSTimeInterval diff = [self.timestamp timeIntervalSinceDate:otherObject.timestamp];
	if (diff>0)
	{
		return NSOrderedDescending;
	}
 
	if (diff<0)
	{
		return NSOrderedAscending;
	}
 
	return NSOrderedSame;
}

Once you have it set up like this sorting is as easy as:

// original unsorted array
NSArray *unsortedObjects = [NSArray arrayWithObjects:oneObject, secondObject, nil];
 
// new and shiny sorted array
NSArray *sortedObjects = [unsortedObjects sortedArrayUsingSelector:@selector(compareByTime:)];

Here is another example that uses the compare method that’s built into NSString:

NSArray *unsortedArray = [NSArray arrayWithObjects:@"String3", @"String2", @"String1", nil];
NSArray *sortedArray = [unsortedArray sortedArrayUsingSelector:@selector(compare:)];

Sorting is very simple with the two methods shown above. If you just want simple sorting then use descriptors. If you need more control over the sorting method then define your own sorting method.

Retain, Release … repeat

In Objective-C you have to make sure that you match your alloc and retains with the appropriate number of release calls. Otherwise you might be leaking objects. Here’s an example that I faced today.

The normal pattern if you create an object that you then add to an array is the following:

Player *tmpPlayer =  [[Player alloc] initWithName:player1Name.text color:[UIColor greenColor]];
[tmpArray addObject:tmpPlayer];
[tmpPlayer release];

Because adding an object to an NSArray causes the object to be retained you have to religiously follow any addObject with a release. You only want to keep the object retained once because when you later release the array, the array in turn releases all it’s objects and if the retain count is higher then 1 then these objects will leak. I.e. their dealloc won’t be called because they still have a retain count higher than zero.

To visualize the dance of alloc, init, retain, release and dealloc you could do the following:

- (id) initWithName:(NSString *)aName;
{
		if (self = [super init])
		{
			self.name = aName;
			NSLog(@"player %@ init, retain count is now %d", name, [self retainCount]);
 
			return self;
		}
 
	return nil;
}
 
- (void) retain
{
	NSLog(@"player %@ retain, retain count is now %d", name, [self retainCount]+1);
	[super retain];
	// cannot log here, causes crash
}
 
- (void) release
{
	NSLog(@"player %@ release, retain count is now %d", name, [self retainCount]-1);
	[super release];
	// cannot log here, causes crash
}
 
- (void) dealloc
{
	NSLog(@"player %@ dealloc", name);
	[name release];
	[super dealloc];
}

As you can see it is easy to override retain, release and dealloc and inject an NSLog to output the current retainCount. But don’t leave those log statements in the final product! NSLog are executed even if you strip all other debug info via a release build and I have seen them dramatically decrease your app’s performance.

In my case today I had forgotten the release after the addObject and thus the Instruments tool showed a couple of bytes leak that I spent an hour looking for in the wrong place.

Localization Woes

Adding additional languages to your iPhone app is extremely easy. Below I’ll show you how. But there is one thing that the documentation did not mention, that also cost me a couple of hours.

Step 1: Change all strings to be localizable

You need to go through your code and everywhere you want a string to be localizable you need to change it to use the NSLocalizedString macro. This marks this string to be localized. Also if no localization can be found then the string returned is identical to the first parameter.

// without localization
player1Name.placeholder = @"Player 1";
 
// with localization
player1Name.placeholder = NSLocalizedString(@"Player 1", @"Player View");

The second parameter is called “comment”, but it’s actually sort of a context for the “Player 1″ token. I would use this to uniquely identify the view where it’s used. 

Step 2: Generate your Localizable.strings file

The SDK comes with a very useful tool genstrings. This tool scans your code for all instances of NSLocalizedString and pulls these together into a UTF-16 strings file. This file you can send to a translator who will change the replacements, in the end you will have one Localizable.strings file per language.

# in your project directory
genstrings Classes/*.m

The strings file will look like this:

/* Player View */
"Player 1" = "Spieler 1";
 
/* Player View */
"Player 2" = "Spieler 2";
 
/* Player View */
"Player 3" = "Spieler 3";

All strings on the right side of an equal sign should be changed. If you want to you can aggregate multiple tokens under one header if it is identical, but don’t change the comments because otherwise the NSLocalizedString will not work.

Step 3: Add strings file to your project and make it localizable

Now go into your project and add an empty Localizable.strings file. Right-click on it, Get info, and add localizations for all languages that you want to support. Note that Xcode adds “English” for you, but this is outdated: your locales should use the 2 letter ISO code. en for English, de for German and so on. So after adding those you should remove English. 

This will create localization directories under your project directory that are called like de.lproj. Those directories contain all the language-specific variantes of all files for this language. You can even localize images like that.

Step 4: Troubleshoot

That’s all it takes…  if you haven’t accidentially change the strings files away from UTF-16. Because then you will find that the build process for your app works fine, but on the device localization will fail to work. If you look into the app bundle your strings file will have been renamed like Localizable.strings.45601. Of course the iphone will be unable to find this file like this and instead use the default localization.

In the info of any strings file you can change and/or force it to UTF-16. Save yourself the pain of not knowing why your pretty new language does not show up. Another reason I found (which nobody warns you about) is those darn semicolons. Should you loose a single one you again don’t see your localization even though the building works fine. Been there, trust me.

In Summary, localization is really simple with Objective C. Seasoned programmers would probably even do step 1 while they are coding. It’s just a little bit more code, but the rest is almost automatic. Now all I need to do is find people who speak French, Spanish and Japanese.

Defaulting Can Be Good

Coming from the Windows world I was used to storing program settings into this beast that is known as “The Registry”. So when I came to iPhone I had no clue where to put those settings that you want to keep between program launches. My first instinct was to put them into an NSDictionary and save this to disk.

That’s how I did it for half a dozen programs only to realize today that there would have been an easier method. The magic class to do it all with is called NSUserDefaults. All you need to do is instantiate it and then read and write values for names of your choosing, just like you would interacting with a dictionary.

//Instantiate NSUsersDefaults:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 
// to set a default value
[defaults setObject:@"oliver" forKey:@"Username"];
 
// to read a default value
NSString * myName=[[NSUserDefaults standardUserDefaults] stringForKey:@"Username"];

In reality you are interacting with an in-memory copy of your defaults. The documentation mentions that the system will synchronize it frequently with the persistent storage on disk. If you really want to make sure it gets written, like when your program is exiting, then you can call [defaults synchronize]. But that should not be necessary. In my tests that defaults where also successfully persisted if I changed them as late as in ApplicationWillTerminate.

Also great to know is that you are not limited to just keeping string values in your defaults. Any object that is legal for property lists can also be used in the defaults. These are: NSData, NSString, NSNumber, NSDate, NSArray or NSDictionary.

// some demo objects to save
NSNumber *number = [NSNumber numberWithInt:3];
NSDate *now = [NSDate date]; // now
NSArray *array = [NSArray arrayWithObjects:@"First", @"Second", @"Third", nil];
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:@"Text" forKey:@"Key"];
 
// save them all the same way
[defaults setObject:number forKey:@"Number"];
[defaults setObject:now forKey:@"Now"];
[defaults setObject:array forKey:@"Array"];
[defaults setObject:dictionary forKey:@"Dictionary"];
 
// retrieve them again
int n = [[NSUserDefaults standardUserDefaults] integerForKey:@"Number"];
now = (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:@"Now"];
array = [[NSUserDefaults standardUserDefaults] arrayForKey:@"Array"];
dictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"Dictionary"];

Note how you can directly retrieve the integer value with the convenience method integerForKey. To get an NSDate you need to use objectForKey, there is no dateForKey as one might assume.

Finally there is one more convenient thing. If the defaults get changed, a notification gets sent to which you can subscribe in multiple classes. This notification gets sent out for every single change, so if you change 4 values in a row, the notification will be sent 4 times.

Subscribe to the notification:

[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(settingsChangedNotification:) name:@"NSUserDefaultsDidChangeNotification" object:nil];

And have a function ready to start working once the notification comes:

- (void)settingsChangedNotification:(NSNotification *) notification
{
	NSLog(@"Settings have changed!");
}

There you have it. For any kind of settings you want to keep you can use the methods explained above and save yourself much work. For data that you need to keep secure (e.g. passwords) you will be better advised to use the keychain which I will explain in a future article.

Travelling in Time

You might be aware that the earth is in fact not flat but a globe with lots of time zones spread more or less evenly around its cirumference. There was a time when programmers had to do most of the heavy lifting of times and dates themselves, but luckily Cocoa has some nice classes to take care of the calculations for you.

Here is an example how I would “move” my birth date to the USA. What’s great about it is that it also seems to takes care of daylight savings time. In Austria and Germany this was only introduced 6 years after my birthday, so July 24th in 1974 has timezone GMT+1 whereas July 24th 1984 ends up with GMT+2.

// set up the components to make up my birthday
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setMonth:7];
[components setYear:1974];
[components setDay:24];
[components setHour:12];
[components setMinute:7];
 
// create a calendar
NSCalendar *gregorian = [[NSCalendar alloc]  initWithCalendarIdentifier:NSGregorianCalendar];
 
// default is to use the iPhone's TZ, let's change it to USA
[gregorian setTimeZone:[NSTimeZone timeZoneWithName:@"America/New_York"]];
 
// now we piece it together
NSDate *date74 = [gregorian dateFromComponents:components];
NSLog([date74 description]);  // 1974-07-24 17:07:00 +0100
 
// show the difference the year makes
[components setYear:1984];
NSDate *date84 = [gregorian dateFromComponents:components];
NSLog([date84 description]); // 1984-07-24 18:07:00 +0200
 
// clean up
[components release];
[gregorian release];

Now you might wonder why the output from the NSLog is +0100 and +0200 and not the US timezone. The reason is that Cocoa internally will always automatically convert dates to your current system timezone.

Try it out for yourself? Create a date instance like I have shown above, log it, then change the default timezone for your program and log it again. Even though you did not modify the date instance, you will get a different output.

// components already set up as before
NSDate *date84 = [gregorian dateFromComponents:components];
NSLog([date84 description]); // 1984-07-24 18:07:00 +0200
 
[NSTimeZone setDefaultTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
NSLog([date84 description]); // 1984-07-24 16:07:00 +0000

So there is lots of date magic (aka “time travel”) available in Cocoa. All you have to get used to is having to write lots of code but you trade this for lots of functionality.