Ad

Our DNA is written in Swift
Jump

Localization Workaround

Trebor asks:

“When I add a new localization, there are just a few choices:

English

Japanese

French

German

I thought that the system needed “fr” to know it is French and “de” to know it is German?

Will it recognize the word “German” and know that is the same as a de.lprog?

If the answer is yes, does it know the full names of all languages?

For example, if I want to add Chinese, can I call the localization “Chinese” or do I need to call it zh?”

I believe the following to be true. Xcode is still putting in English as first localization if you make a file localizable. This works, but the “modern way” to do things (or so I read) is to use the two letter iso codes. The problem is, that if you remove the English then you can never add any new localization. That’s a bug in Xcode.

So I do it like this:

  • create the file
  • make it localizable
  • add localizations for all locales you require, including English: en, de, it, es, fr, nl
  • LEAVE English empty, but put English into the “en” locale

Do not remove the “English”, because then XCode will not allow you to add new localizations in the future.

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.

Story of my Life

The company that puts the bread on my table asked me to write an article about how my programming hobby led to an idea that eventually got submitted for patenting to the US patent office. People liked it very much so even further departments asked me if they can use the article.

Now that it has gone public inside the company I sought and got permission to put it here for you to enjoy. Note from editor: Minor changes where made to protect proprietary company information.

 

How My Hobby Led to an Invention that Amdocs Patented

Oliver DrobnikWhen I was just a young boy I loved to play with LEGO pieces and was fascinated with how you can put different things together to form something completely new. The concept of “the whole being more than the sum of its parts” fascinated me and my passion for constructing things carried over to writing computer programs on the MSX home computer that my father bought in 1983, when I was 9 years old.

Previously our family had moved to the suburbs of Vienna, the Austrian capital, and being away from the many distractions of the big city left me lots of time to sit in our basement and experiment with simple computer programs which I would create first in Microsoft BASIC and later in Turbo Pascal.

A neighbor of ours was the kind of person you nowadays would consider a “nerd,” but he was the first person with whom I was able to share my passion for computers. So, I followed his example after graduating from high school and attended college for two years to learn how to become an IT engineer. Three years later the Austrian Chamber of Commerce awarded me with my Engineers degree.

At first I tried to go to university and even passed a couple of IT and English exams quite successfully, but after two years I got bored. An academic IT career, in my opinion, was miles away from the hands on work of creating real programs that solve real problems.

So right after compulsory military service, I applied for a job at TelCo and got on board with them as an IT operations guy. I would perform billing and rating runs, while constantly creating scripts to make such mundane tasks more automatic and self-correcting. Unix shell scripts were my “weapon of choice” for saving myself a lot of time.

My next ‘idol’ was a guy who showed me how to write C++ programs that I could debug on Windows and later compile for HP Unix or Tru64 without changing the code.

A couple of companies later, I became one of the first owners of the iPhone in Austria. Even long before it was officially available, I knew I just had to have one. Since the inception of GSM I had been a true follower of Nokia, but the promise of a touch-based full-screen interface together with ‘always-on’ internet connectivity seemed to me to be the holy grail of mobile computing. Right after the official release of the iPhone 3G, I upgraded and as soon the Software Development Kit was available I signed up for a developer’s account. At first learning this strange new flavor of Objective C was daunting, but endless hours of experimenting and puzzle solving got me to a skill level that now allows me to create applications within a couple of weeks in my spare time.

I found that the more code I wrote while travelling on the train, the more ideas I got for new iPhone programs that would be incredibly useful to me. One of those ideas was clearly on too large a scale for me to implement by myself, but when I heard about the Amdocs innovation intranet site I decided to submit my suggestion to Amdocs.

[Proprietary details of patent omitted here]

Several months passed before I received a reply from Amdocs’ innovation team, but when the reply came things moved quickly. I was informed that my idea was worthy of patenting because of its proximity to Amdocs’ core business – mobile commerce. Based on what I had previously written, a patent lawyer created a 20-page document which detailed my invention in minute detail. When I approved the draft it was submitted to the US patent office.

Many people have since asked me why I would give my idea to Amdocs in exchange for a one-time premium. My response to them is that I would rather see a good idea be owned by a great company that has the potential of implementing it than keep it to myself in the hopes that a profit may be gained from it someday.

Today, at age 34, writing my own programs feels like ‘LEGO for Adults,’ a sort of creative self-expression. My full time job at Amdocs is to take care of IT at the Amdocs Interactive Vienna site; sitting down in my spare time and creating solutions for my own computer problems makes me feel like I can create something lasting that also has value to others and, at the same time, balances and energizes my creative juices.

From my experiences, I can only recommend to all readers to take on a hobby where they create something with their hands – be it carpentry, knitting, programming or anything else. I have found that such a constructive hobby increases your general satisfaction level and makes it much easier to deal with the stress you might face at work.

GeoCorder Breaks Records

After the first full day of sales I am really happy with how GeoCorder is doing. I did not expect such a huge number of downloads. But hey, it’s useful and it’s free… for a limited time.

USA leads the way with 240 downloads, Germany with 43 is not-so-close second and Canada on third place is barely more than the average with 29. In total the top three countries amount to more than half of downloads, 495 in total.

Actually those numbers might give a very good indication about what international distribution of downloads to expect and which languages to target. With English you will cover more than two thirds of market volume and German approx. 10%.

GeoCorder is useful to anybody in any language and therefore I think download number are not skewed by people not downloading because they don’t understand the app’s core content. GeoCorder has so little text that your language does not matter.

I will raise the price to $2 once I reach 1000 downloads. While this will definitely reduce downloads dramatically I hope to turn a couple of bucks for the effort I put into this app.

UPDATE: I dropped the price again to $1 because daily downloads dropped from over 200 to zero.

New App available: GeoCorder

Our latest addition to the app store is GeoCorder, currently free. With it you can simply record GPS tracks in full detail and later e-mail them to contacts in your address book.

Actually I just put it online to get some report data about free apps, so that’s your advantage. You can get it now for free. Later I’ll probably raise the price to 1 Dollar, because I believe that if people like the concept they will also buy it for that amount and the earnings can then go towards further development of the app.

GeoCorder had been rejected by Apple several times. First due to my abusing a standard button for something completely different. Later I had a nasty crashing bug that you would only see if you tried to e-mail a track without network connectivity. Apple thought that this might “confuse users”. And frankly so did I after being able to duplicate the problem on iPhone Simulator.

I persisted, improving my code several times and resubmitting as often. Finally, today, I got the infamous “Your application is Ready for Sale” e-mail. Hooray!

More info here.

String to Array of Characters

Michael asks:

“How do I split the characters in an NSString into an NSArray? In BASIC I could split a string with empty character, but in Object C this does not work”

One way to do it is to just get one character substrings:

NSString *s = @"Hello World";
int i;
NSMutableArray *m = [[NSMutableArray alloc] init];
 
for (i=0;i< [s length]; i++)
{
	[m addObject:[s substringWithRange:NSMakeRange(i, 1)]];
}
 
NSLog([m description]);  // most NS* objects have a useful description
 
[m release];  // don't forget

In Objective C every time you need a starting point and then a length from there it’s call an NSRange. Conveniently there is also a *Make macro for most structures like this. So in this case you just NSMakeRange(position,length) and pass this to a substringWithRange.

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.