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

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.

Mysterious PLIST

I have grown very fond of using NSDictionary and NSMutableDictionary if I want to put diverse data into a single object to pass around in my programs. Also NSDictionary has the built-in capability of saving and reading itself to disk, in the so-called property list (abbreviated as “plist”) format.

Now I was implementing caching of such a NSDictionary in my app and for mysterious reasons the following code would not result in any file being saved:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"notworking.plist"];
 
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSNumber *numKey = [NSNumber numberWithInt:123];
[dict setObject:@"a Value" forKey:numKey];    // legal: using a NSNumber as key
 
[dict writeToFile:path atomically:YES];
[dict release];

Can you spot the problem? Probably not, if you check the documentation you will read that NSDictionary can hold and save any of the following data types: NSNumber, NSData, NSString, NSDictionary, NSArray. I did not use anything else, therefore the dictionary should be saved as a plist. With lots of experimenting however I found a simple change to the above code that makes it work:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"working.plist"];
 
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSString *stringKey = @"123";
[dict setObject:@"a Value" forKey:stringKey];    // workaround
 
[dict writeToFile:path atomically:YES];
[dict release];

Simply put you cannot use anything except as a string as the root key of an NSDictionary. The above code will save an XML file looking like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>123</key>
        <string>a Value</string>
</dict>
</plist>

If you want to save a dictionary, make sure you use only strings for keys. In another case I was using an NSDate as valid key for an entry in a dictionary. Same result: writing it to a file does not work. Why there is such a limitation I cannot say. In any case I submitted it as bug report to Apple.

Having pondered this a little I realized the most probable explanation. If you look at the way an XML plist is represented in the file you can see that there is no indication as to what data type the <key> represents. Therefore it can only be a string.

Showing Version Info

I found that I need some place to show a version in my apps, because otherwise I and my testers never know if we are talking about the same version. A problem they might be having could have already been fixed previously. Without a version info you are at a loss.

Here’s a version info box, that I came up with. This shows how to access the version string from Info.plist as well as open a new mail to the author.

- (IBAction) showAppInfo:(id)sender
{
	// a convenient method to get to the Info.plist in the app bundle
	NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
 
	// get two items from the dictionary
	NSString *version = [info objectForKey:@"CFBundleVersion"];
	NSString *title = [info objectForKey:@"CFBundleDisplayName"];
 
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[@"About " stringByAppendingString:title]
						  message:[NSString stringWithFormat:@"Version %@\n\n© 2009 Drobnik.com", version]
						  delegate:self
						  cancelButtonTitle:@"Dismiss"
						  otherButtonTitles:@"Contact", nil];
	[alert show];
	[alert release];
}
// this function from UIAlertViewDelegate is called when a button is pushed
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
	UIApplication *myApp = [UIApplication sharedApplication];
 
	// cancel button has index 0
	switch (buttonIndex) {
		case 1:
		{
			[myApp openURL:[NSURL URLWithString:@"mailto:oliver@drobnik.com"]];
			break;
		}
		default:
			break;
	}
 
}

Two buttons are displayed side by side. Three or more are vertically stacked. This is how the result from the code about looks like:

All Purpose About Box

Remove Whitespace from NSString

In Cocoa Touch you always need to write more to achieve the same …

NSString *string = @" spaces in front and at the end ";
NSString *trimmedString = [string stringByTrimmingCharactersInSet:
                                  [NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(trimmedString)

While such a standard task might look excessively much code to write, you gain many additional possibilites of what you could want to trim away. NSCharacterSet also knows these other sets. 

  • alphanumericCharacterSet
  • capitalizedLetterCharacterSet
  • controlCharacterSet
  • decimalDigitCharacterSet
  • decomposableCharacterSet
  • illegalCharacterSet
  • letterCharacterSet
  • lowercaseLetterCharacterSet
  • newlineCharacterSet
  • nonBaseCharacterSet
  • punctuationCharacterSet
  • symbolCharacterSet
  • uppercaseLetterCharacterSet
  • whitespaceAndNewlineCharacterSet
  • whitespaceCharacterSet