MapKit

Location, Location, Location is all the rage at the moment. Where did someone last update their social network status ? Where is the nearest restaurant ? Where do I need to be in 5 mins ? the possibilities for location services are (nearly) endless. But what is a location ? well put simple it is just a co-ordinate value (longitude and latitude), and in the case of iPhone, it is in reference to the earth. But lets be honest, displaying -122.03, 37.33 in your sexy new iPhone application is not very exciting now is it ? That is were MKMapView comes in.

MKMapView allows you to display a Map on your iPhone’s screen. MKMapView is just a subclass of UIView so you can treat it like one. You can ether use it as a subview (such as on a profile page), or make it go full screen (using it with its own view controller) such as when you are viewing a map to get directions.

So like UIView, to create a MKMapView you need to initialise one with a frame:

MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0,0,320,480)];

Unlike UIView, you will also want to become the delegate of MKMapView.

mapView.delegate = self;

The delegate methods for MKMapView include ones that inform you about when the map starts and finishes to load, in addition to if it fails to load at all. (These delegate methods are very similar to UIWebView if you have used that).

- (void)mapViewWillStartLoadingMap:(MKMapView *)mapView;
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView;
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error;

In addition to the loading of the Map, the delegate also provides call backs for when the map’s region changes, it also provides callbacks regarding annotation information (more on that later).

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated;

After you have created a MKMapView, you will want to show a specific region on the map. The method to do this is:

- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated;

This introduces a new MapKit data type MKCoordinateRegion. This is the centre point (that you want to show) expressed as a co-ordinate, and then the distance (span) around this co-ordinate that you want to be shown. In essence the span is the zoom level. The bigger the span, the bigger the area that is displayed on the map.

To make a new region you will need to use:

MKCoordinateRegionMake(CLLocationCoordinate2D centerCoordinate, MKCoordinateSpan span);

And thus you will probably have some code that looks like this:

CLLocationCoordinate2D coordinate;
coordinate.latitude = -122.03;
coordinate.longitude = 37.33;

MKCoordinateSpan span = MKCoordinateSpanMake(0.003, 0.003);

[mapView setRegion:MKCoordinateRegionMake(coordinate, MKCoordinateSpanMake(0.003, 0.003)) animated:YES];

After you have focused in on a specific region of the map, you will more than likely want to annotate a given point. To do this you need to create an object that conforms to the (informal) MKAnnotation protocol, and in particular implements the CLLocationCoordinate2D coordinate property.

e.g.

@interface MCSMMapAnnotation : NSObject {

}

@property (nonatomic) CLLocationCoordinate2D coordinate;

@end

Then you just need to create an instance of this model object, and then add it to the map as an annotation:

MCSMMapAnnotation *annotation = [[MCSMMapAnnotation alloc] init];

CLLocationCoordinate2D annotationCoordinate;
coordinate.latitude = -122.03;
coordinate.longitude = 37.33;
annotation.coordinate = annotationCoordinate;
[self.mapView addAnnotation:annotation];

And that is it, nice and simple.

One more thing …

Before I mentioned that there are delegate methods regarding the annotations on the Map View. One that is particular useful is:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)annotationViews;

Why is it useful ? Well if you didn’t notice, doing the above means that the annotation(s) just appear on the screen and they do not drop on. So in this delegate method, you can move the annotations off screen and then put them back to where MapKit said they should be.

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)annotationViews {

    // The final (correct) position of the annotation
    CGRect finalFrame;

    // The position we will drop the annotation from
    CGRect offScreenFrame;      

    for(UIView *annotationView in annotationViews) {
        // MapKit has worked out the annotations final position so store it
        finalFrame = annotationView.frame;

        // We just want to move the annotation, so it is just above the top of the visible screen

        offScreenFrame = CGRectMake(finalFrame.origin.x,(finalFrame.size.height *-1) , finalFrame.size.width, finalFrame.size.height);

        // Set and therefore move the annotation to the off screen position
        annotationView.frame = offScreenFrame;

        // Set up an animation block to animate the drop
        [UIView beginAnimations:@"AnimateAnnotation" context:NULL];
        [UIView setAnimationDuration:1.0];

        // Set the final frame, to be the frame that map kit originally calculated
        annotationView.frame = finalFrame;
        [UIView commitAnimations];
    }       
}

This is just a brief overview of what you can do with MapKit. The most obvious thing you can do, is to add more than one annotation to the map, and the above animation trick is designed to work with this. One feature you may want to do if your application heavily relies upon maps, is to create your own custom pins (that don’t even need to look like pins), to point out specific points on the map.

iPad - The sweet spot in software development

Now that the iPad (iPhone OS 3.2) SDK is no longer under NDA (I think anyway), I thought I would post my opinion on developing for it.

I was programming in Objective-C before the iPhone was even announced (only for a around a year and a half, but I shall still claim it), and switching to the iPhone OS was somewhat strange. At first all I could see is what was missing, all those APIs I took for granted, and things like bindings that I had come to love and (overly) rely on.

In retrospect these limitations where a good thing. Learning how to do things the long way round (such as UIs in code) meant you actually got to know how they worked, and it wasn’t just because interface builder was magic. I still to this day write a lot of my iPhone UIs purely in code. Why you ask ? If your UI is complex then sometimes interface builder just doesn’t do what you need. This is especially the case on the iPhone, as you are often animating views on and off the screen, making them bigger and smaller etc etc. Also having half of your logic in a xib, and half of it in source code can lead to its own problems. I hope that I am not the only one who has wasted significant amount of time after forgetting to hook up an IBOutlet or IBAction (the one time you don’t want messages that are sent to nil, to be ignored !!!).

After spending about a year exclusively programming for the iPhone OS in my day job, I got an urge to write a little application for Mac OS X. And in a strange turn of events I had the same feelings when I moved back to the mac, as when I moved to the iPhone OS. You can’t animate an NSView with the same level of ease as you can a UIView, and some of the Mac OSX APIs are not very clean at all (try playing with NSOutlineView to name one class). This is probably expected as some of these APIs have been around for over a decade, but the biggest surprise is what you forgot you had to do when creating Mac applications. Keyboard shortcuts, window resizing and providing automatic updates of your applications to name but a few. Apple ether take care of this for you on the iPhone, or they are just not a feature of the OS. Having the limitations of a fixed size screen, and only being able to get applications from the AppStore doesn’t seem such a bad deal after all.

Then came the iPad, its like a Big iPhone from a programmers point view. Its the iPhone OS with a couple of extras, but with a really big display. The jewel in the iPhone OS’s crown is its UI Libraries. It is so simple (compared to any other platform, mobile or otherwise), to build really slick animated interfaces that your users will love to use. Now that you have all this screen to play with, you can start building “Desktop Class” applications but with the iPhones APIs. The thing that has shocked me the most, is the quality of all of the iPad applications that have been released. The vast majority of developers have all gone the extra mile to make their UIs, and therefore their applications look extremely polished. This brings around the great debate of application pricing. Everyone with any business acumen would have long worked out that £0.59/$0.99 is not a sustainable business model for 99% of developers, and the iPad has seemed to have broken this trend from day one. Applications seem to be in the range of £3/$5 - £10/$20, which will probably lead to less applications being sold, but in the long term will lead to a higher quality applications that can be supported by their respective developers.

The one thing I am hoping for is that the iPhone APIs get ported back to Mac OS X, or maybe that iPhone OS becomes the Mac OS. Saying that, no multitasking and the AppStore being the sole way of getting apps onto my mac is a scary thought…

MacRuby - First Impression

What not Objective-C !!! whats going on, I hear you cry. Well I thought I would mix things up a bit, after all it has got Mac in the name.

I am a firm believer that learning a new language, makes you better at developing with the ones you already know. Different languages often attack problems from different angles, depending on the language’s capabilities (built in classes etc) and common design patterns. Im sure people that are new to iPhone/Mac programming, where shocked about how often they had to type the word delegate.

So why Ruby?

I admit it, I have played with Ruby before and the fact that I quite like it did help its cause, but that isn’t the main reason. The main reason is the fact you can use it for almost anything. You can use it as a CGI script on the web, you can build Web applications using Ruby On Rails, you can do shell scripting to automate tasks, and obviously you can build desktop applications.

So what is MacRuby?

MacRuby is Ruby 1.9 built on top of the Objective-C runtime, which allows you to build Mac applications using Ruby (Its actually an Apple run project). The real sexy bit is you can call any Objective-C method directly using Ruby, meaning that you have access to all of OS X functionality. What is even more surprising, is that you can use Interface builder to put together your UI (well it was a surprise to me anyway).

Unfortunately MacRuby doesn’t currently work on the iPhone as it needs the garbage collector, but at some point this may change.

Interface Builder

So if your using interface builder, your need to declare IBOutlets so you can access a UI elements from in your code.

IBOutlet NSTableView *tableView;

In Ruby you don’t actually type cast your variables, so the only way to achieve this is to declare an accessor to that variable.

attr_accessor :tableView

In addition to IBOutlets your also need to declare IBActions for button presses etc.

- (IBAction)pressMe:(id)sender {

}

In Ruby you need to define a method with the sender parameter

def pressedButton(sender)
end

Calling Objective-C methods from Ruby

As you are building Mac application with Ruby, you will want to access the vast array of APIs available in Mac OS X. These are (mostly) written in Objective-C. One common task is creating a string:

NSString *string = [NSString stringWithString:@"Test string"];

In Ruby you do the following. Notice that there is no pointers and you do not need to declare a type.

string = NSString.alloc.initWithString("Test String")

*As Ted Wise correctly points out in the comments, you can create a NSMutableString by do the following piece of Ruby code, but I thought the string example was a nice and concise one to use.

string = "Test String"

From the above code snippets you will also notice that like most languages, Ruby doesn’t “stagger” its variables like Objective-C.

So how would you call the following Objective-C code snippet in Ruby ?

NSAlert *alert = [NSAlert alertWithMessageText:@"title"
defaultButton:@"ok"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"This is an alertview"];
[alert runModal];    

Well quite easily actually, as MacRuby actually adds this syntax to Ruby, so it would look like the following:

alert = NSAlert.alertWithMessageText("Title", defaultButton:"OK", alternateButton:nil, otherButton:nil, informativeTextWithFormat:"This is an alertview")

alert.runModal()

Easy enough ?

Conclusion (of my first impressions)

This post quite literally touches the surface of MacRuby, but I think it is an exciting technology. Ruby is really good for getting stuff done quickly, as the language does a lot of heavy lifting for you. By doing the Model part of the MVC in Ruby, you could in theory share the code across applications on the desktop and the web, which can’t be bad.

If your new to the Mac and don’t know ether Objective-C or Ruby, I would still learn Objective-C first if I am being honest. Learning Cocoa is hard enough without all the examples being in another language. But if your new to the Mac and already know Ruby, it might be a good way to start mac development.

Show hidden files in the Finder

Sometimes when you are digging around in the under pinnings of Mac OS X, you need to access folders and files that are normally hidden by the OS. Thankfully there is preference in the Finder for this, which you can turn on and off in the Terminal.

The command is:

defaults write com.apple.Finder AppleShowAllFiles YES

The Finder needs to be restarted for this change to take effect. Luckily there is a Terminal command for this too:

killall Finder

Once you are done playing around with hidden files, you can stop showing them by replacing YES with NO in the original terminal command.

defaults write com.apple.Finder AppleShowAllFiles NO

It is as simple as that, but make sure your don’t break anything.

Redirecting NSLog to a log file

Using NSLog is all well and good for debugging iPhone OS applications when the device is connected to your Mac, but what about when it is not. Often you want to give builds of your application to people that don’t have Xcode installed (or can’t get the silly certificates to work !!!), or just as commonly, to test if the application works outside of your lovely office which has a perfect WiFi connection.

When I first thought about this problem, I was thinking along the lines of build a custom log function lets say MagicLog() and this calls a function that saves the string to a file. The problem with this is that I would have to go through all of my code and add MagicLog() to everywhere that I have NSLog(), that seemed a bit to verbose for my liking.

After searching on the internet I found out that (rather obvious) NSLog runs over standard error. This means that you are able to simply redirect standard error to a file using the ANSI function freopen().

FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict stream);

http://www.opengroup.org/onlinepubs/000095399/functions/freopen.html

Before we can save NSLog() to a file, we first have to find a place to save it. The best place to do this is the application’s documents folder. To get this you can use the following code snippet:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

Once we have the documents folder, we need to give a name to the file. To make it easy for me I just name it “the date”.log :

NSString *fileName = [NSString stringWithFormat:@"%@.log",[NSDate date]];

To make this into a valid path, use the NSString method stringByAppendingPathComponent

NSString *logFilePath = [documentsDirectory stringByAppendingPathComponent:fileName];

The last step is to redirect stderr to this file

freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);

To keep my code maintainable I keep all of this as a function, and I call it in my AppDelegate if I want to turn this functionality on.

- (void)redirectNSLogToDocumentFolder {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);

    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSString *fileName = [NSString stringWithFormat:@"%@.log",[NSDate date]];

    NSString *logFilePath = [documentsDirectory stringByAppendingPathComponent:fileName];

    freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
}

Getting the file.

Now that you have logged all the information you need, you will want to get this off of the device. Thankfully this is easy as well. Simply open the organiser in Xcode and select the device that is currently connected to your Mac.

A the bottom of the main pain there is an application section. Open the detail disclosure on the application you want to get the data from, and hit the little download button to the right of the Application Data package. You will be prompted to save this folder to your mac.

Warning: Writing to the file system on the iPhone is slow, so this will effect the performance of your application. Therefore DO NOT ship an application with this in, or give it to people that can’t stand poor performance.

URL Encoding

If you have tried to send any information using a GET web request, you would have come across an annoying problem. That annoying problem is making sure that the URL is correctly encoded.

At first glance it would seem that the Cocoa Frameworks do this for you, and you would be right …. well kind of.

The issue is that by default most of these methods leave characters such as & = ? within a URL, as they are strictly speaking valid. The problem is that these characters have special meanings in a GET request, and will more than likely make your request in valid.

Luckily there is a function in Core Foundation that helps:

CFStringRef CFURLCreateStringByAddingPercentEscapes (
   CFAllocatorRef allocator,
   CFStringRef originalString,
   CFStringRef charactersToLeaveUnescaped,
   CFStringRef legalURLCharactersToBeEscaped,
   CFStringEncoding encoding
);

What makes this function useful, is the legalURLCharactersToBeEscaped parameter. This will escape legal characters such as & ? = if they are supplied. This allows you to escape parameters using the following code.

CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)parameter, NULL, CFSTR(":/?#[]@!$&’()*+,;="), kCFStringEncodingUTF8)

An example of when to use this, is Twitters Update status API. You can find that here http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses%C2%A0update

To update your status to the following:

This is my status

You would need to post up the following URL:

http://twitter.com/statuses/update.xml?status=This%20is%20my%20status

As this is such a common problem of mine, I have created a category on NSURL. This allows you to pass in a base URL and a parameters dictionary.


+ (NSURL *)URLWithBaseString:(NSString *)baseString parameters:(NSDictionary *)parameters {

NSMutableString *urlString = [NSMutableString string];

// The URL starts with the base string
[urlString appendString:baseString];

NSString *escapedString;

NSInteger keyIndex = 0;

for (id key in parameters)
 {

    //First Parameter needs to be prefixed with a ? and any other parameter needs to be prefixed with an &
    if (keyIndex == 0) {
        escapedString = (NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
         (CFStringRef)[parameters valueForKey:key],
         NULL,
         CFSTR(":/?#[]@!$&’()*+,;="), 
        kCFStringEncodingUTF8);

        [urlString appendFormat:@"?%@=%@",key,escapedString];
        [escapedString release];
        } else {
        escapedString = (NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, 
        (CFStringRef)[parameters valueForKey:key],
         NULL, 
        CFSTR(":/?#[]@!$&’()*+,;="),
         kCFStringEncodingUTF8);

        [urlString appendFormat:@"&%@=%@",key,escapedString];
        [escapedString release];
    }

    keyIndex++;
    }

return [NSURL URLWithString:urlString];

}

Using a parameters dictionary keeps the code nice and clean, but beware, to use the category method above you still have to make sure that your keys, and the base URL are correctly encoded (no spaces or invalid characters !!!!!).

As we now have a category method to do all the hard work for us, to create the Twitter URL you just need to do the following: “`objc NSString *baseString = @”http://twitter.com/statuses/update.xml“;

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"This is my status”,@“status”,nil];

NSURL *url = [NSURL URLWithBaseString:baseString parameters:dictionary]; “`

And thats it. Obviously this category can be used for things other than twitter ….. if you really want to.

Reopening an application's main window by clicking on the Dock Icon

When building Mac applications, Apple usually takes care of most of the default behaviours for you. One thing that Mac applications don’t do by default is, reopening the applications main window (if it has been closed), when the dock icon is pressed.

Although this information is very hard to find in the documentation, but is actually very easy to. The method you need to find is:

- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;

This is an optional delegate method that your AppDelegate can choose to implement, and is called when the user presses your application’s dock icon. The bool flags indicates whether the application has any visible windows. To reopen your application’s main window, you need to have a pointer to it (In the example below assume that it is defined as NSWindow *window; in the header file). If you do have a pointer to then you simple need to implement the code below.

- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {

    if (flag == NO) {
        [window makeKeyAndOrderFront:self]; 
    }
    return YES; 
}

It is as simple as that.

Simple UIView based Animations on the iPhone

Although for complex animation sequences on the iPhone you need to use either the OpenGL or Apple’s very own Core Animation Framework, a lot of simple animations can be achieved with the methods found in the UIView class. All these animations are actually built upon Core Animation, but they have been wrapped up for you to use with very little code.

All animations triggered by the UIView class happen within a animation block.

To start an animation block you use the UIView Class method:

+ (void)beginAnimations:(NSString *)animationID context:(void *)context;

The animation ID is used in delegate call backs for such things as the beginning and end of animation blocks. The context is an additional piece of information that can be passed through.

As both of these parameters are optional, you comenly see:

[UIView beginAnimations:@"" context:NULL];

To commit these animations, and therefore end the animation block, you need to use the UIView class method:

+ (void)commitAnimations

So what types of animations can you do ? well quite a few.

You can animate views by:

In a single animation block you can animate multiple views, and you are also able to nest animation blocks.

So lets do a common example. When a UITableViewCell is being edited you often want to make a UILabel’s (label) alpha change to 0 so it is hidden:

[UIView beginAnimations:@"" context:NULL];
[label setAlpha:(editing ? 0.0 : 1.0)];
[UIView commitAnimations];

So for the second example we also have a UILabel (label), that we want to grow when the animation code is run. We also want this animation to last 2 seconds, so the user can admire our work. In addition to this, we want the animation to “ease in”. This is known as the animation curve. The animation curves that are available are

We would do this animation using the following animation block:

[UIView beginAnimations:@"" context:NULL];

// The new frame size
[label setFrame: CGRectMake(0,0,320,100)];

// The animation duration
[UIView setAnimationDuration:2.0];

[UIView setAnimationDelay: UIViewAnimationCurveEaseIn];

[UIView commitAnimations];

This is just a brief overview of the animations you can do using the UIView class, but it should be enough to get you started. The UIView methods also include delegate call backs for when an animation starts and end. For more information see Apple’s Documentation.

If the UIView class does not have what you need, you will probably need to use the Core Animation framework. While using this framework is not trivial, it is not as hard as using OpenGL (which is used commonly for 3D games), and you can build some fantastic animations using it.

Changing the name of an iPhone application

When you finally come to release your application after months of hard work, your might want to change the name of the application that appears on the iPhone springboard, and if you use it, the settings application.

To change the name of an iPhone application is very easy. Simply open the Xcode project, and then scroll down to the “Targets” section, which the the “Groups & Files” part of Xcode. Select the application’s target and “Get info” on it using cmd-i or ctrl clicking it, and select “Get Info” from the menu.

In the build section of the get info window, you need to change the “Product Name” value to whatever you want your application to be called. When you change this value make sure that the configuration pop up menu (at the top of the window) is set to “All Configurations”, so it takes effect in all of your builds.

For the change of name to take effect, simply clean your targets and rebuild your application.

NSUserDefaults (Preferences)

Not everybody likes their applications to behave in the same way, and this is why the majority of Mac and iPhone applications have preferences. The default way to handle these preferences is using the class NSUserDefaults.

NSUserDefaults are stored on the file system as .plist, which is simply an XML document. The way you access NSUserDefaults programatically is very much like accessing an NSMutableDictionary, that is by using keys.

Setting User Defaults

NSUserDefaults can be a bool, float, integer or an object. So if you wanted to set the autosave option (which is a BOOL) for your application to be YES, you would write:

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"AutoSave"];

Moreover if you wanted to set the person’s name to ObjColumnist you would write:

[[NSUserDefaults standardUserDefaults] setObject:@"ObjColumnist" forKey:@"PersonName"];

Reading User Defaults

Reading the user defaults is just as easy as setting them. If we wanted to retrieve the 2 values we stored above, you would simply write:

NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:@"PersonName"];
BOOL autoSave = [[NSUserDefaults standardUserDefaults] boolForKey:@"AutoSave"];

You should also notice that there is a convenience method for retrieving a string for a key, even though we set it as an object.

It is also important to be aware that if you attempt to retrieve a numeric value such as an integer, integerForKey: for a non existent key, you will get the value 0. This is obviously an issue if 0 would trigger a certain preference in you application.

Setting the default user defaults

So you can now set and read the user defaults, but how do you assign their default values ?

The answer is registerDefaults:

This method is usually called in the initialize (class) method of a given application’s AppController. The parameter for this method is an NSDictionary. The code below sets the default preference for the AutoSave option to YES, and the person’s name to @“unknown”.

NSMutableDictionary *defaults = [NSMutableDictionary dictionary];

[defaults setObject:[NSNumber numberWithBool:YES] forKey:@"AutoSave"];
[defaults setObject:@"unknown" forKey:@"PersonName"];

[[NSUserDefaults standardUserDefaults] registerDefaults: defaults];

One thing that Cocoa does for you automatically without needing any extra code, is that it only saves to the .plist the values that are different to the defaults values, that were registered using registerDefaults: . This means that if the user does not change any of their default preference settings, there will no be a preference file created.

Note: The code above just stores the preference for the AutoSave option, you still have to create the code to manage the saving of data yourself.

Page 8 of 9