Archive for the ‘iPhone’ category

How to harness the Retina Display in your app

July 15th, 2010

It is remarkably easy to do so and I implore all all iPhone developers to please upgrade all their apps to support the Retina Display on the iPhone 4, otherwise your app just looks blurry.

What Apple achieved with the Retina Display is a remarkable feat. The screen is still exactly the same size of 3.5 inches but where you used to have 1 pixel you now have 4. This means UI elements are scaled exactly by 2.

Developers were always told to design for a screen resolution of 320×480 pixels, so don’t I need to change my UIViewControllers to have a different size?

Well no, and this is how Apple have been incredibly clever. The screen dimensions are now 320×480 points, and not pixels so in your UIViewController you still have the size 320×480. With me so far?

Images

Ok so lets say I have an image that needs to span the width of the screen and is only 100 pixels high on an iPhone 3G or 3Gs we simply embed an image of 320pixels by 100pixels. So that image that we’ve called banner.png, for example, looks perfect on those old devices but they look blurry on the iPhone 4.

I can hear you asking, if the screen is 320points wide but is actually 640pixels wide, how do I provide both an iPhone 4 and iPhone 3G(s) version of this and how much code does it take.

Well my answer is, very easy and zero, that’s right, zero code modifications.

Now you did have a high resolution, or vector version of your artwork right? Ok well open it up and scale it to exactly double the size of the original file. So you’ll now have an image that is 640×200 pixels and save it as banner@2x.png (as the original was called banner.png). Add the new image to your resources in your XCode project.

You can call your image in exactly the same way as before

UIImageView *myImage = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,320,100)];
[myImage setImage:[UIImage imageNamed:@"banner.png"]];
[self.view addSubview:myImage];
[myImage release];

When you run your app on your iPhone 4 (or the iPhone 4) simulator the image will magically use the banner@2x.png version instead. Clever isn’t it!

Default splash images

This is exactly the same as for images, just include a Default@2x.png image in your bundle and the iPhone 4 will use it instead.

Springboard App Icon

Ok now this one is a little more complicated, but not by much. If you’ve already designed a Universal (iPhone and iPad) app then you’ll be familiar with this technique, and actually there are a couple of ways to do it but I’ll explain the one from the Apple Docs.

If you want to still provide support to iOS 3.1.x and below you’ll still need a

CFBundleIconFile

with Icon.png in it for your Info.plist but you should now also include an array entry of

CFBundleIconFiles

Include an Icon.png file, this is your non-iPhone 4 57×57 file
Include an Icon@2x.png file, this is your iPhone 4 114×114 file.

Yep once again it really is that simple.

Conclusion

If you have an app that simply uses SDK UI Elements and/or images, which the majority of iPhone app do, then you simply have no excuse not to support the Retina Display… so what are you waiting for, go cut graphics and submit!

Creating a mini map using MapKit on the iPhone

November 20th, 2009

So as promised here is the final part of my MapKit tips and tricks using the iPhone SDK.

Sorry it’s a little later than I intended as I wanted my version of the feature live in the AppStore before I published the article.

So we’ve seen before how to create a simple map, well now let’s see how to create a really small thumbnail sized version like this (sorry there is no anti-aliasing, the iPhone simulator doesn’t support it on MapKit, works fine on the actual device though!)

smallmap

In my version, I take this code and add it to a much larger view controller like this

bigmap

A bit of shameless plugging: this comes from Tweetings for the iPhone

I won’t cover anything more to this other than getting the map to a state where you can add it to any view of yours using the [object addSubview:mapView] call.

Generating the map

This is the same as generating a large map, that we’ve covered before, except the frame will be of a different size, however the most important factor here is we are going to scale the map

float scaleBy = 0.80;
MKMapView *mapView = [[[MKMapView alloc] initWithFrame:CGRectMake(-5, 0, 100/ scaleBy, 50/scaleBy)] autorelease];
mapView.delegate=self;
mapView.layer.cornerRadius = 10.0; // Make the corners rounded
mapView.opaque = NO; // If you are using in a UITableView never set to YES!
mapView.scrollEnabled = NO; // Don't allow user interaction
mapView.zoomEnabled = NO;
mapView.layer.borderColor = [UIColor colorWithWhite:0.0f alpha:0.5f].CGColor;
mapView.layer.borderWidth = 1.0f/ scaleBy;
mapView.layer.transform = CATransform3DMakeScale(scaleBy, scaleBy, 1.0);

At this point you can actually now add it to your view and set any other properties in the same way as you would normally, job done…. yes it really is that simple!

How to add a pin to embedded map

October 27th, 2009

Part one of this section on MapKits showed how to embed a map and place a floating toolbar for switching the map views, however it didn’t cover how to drop the pin where you wanted it.
MKMapKit

Create the Object

First lets create a new NSObject for the place mark. Let’s call it ‘PlaceMark’

PlaceMark.h

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface PlaceMark : NSObject {
	CLLocationCoordinate2D coordinate;
	NSString *subtitletext;
	NSString *titletext;
}
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (readwrite, retain) NSString *titletext;
@property (readwrite, retain) NSString *subtitletext;
-(id)initWithCoordinate:(CLLocationCoordinate2D) coordinate;
- (NSString *)subtitle;
- (NSString *)title;
-(void)setTitle:(NSString*)strTitle;
-(void)setSubTitle:(NSString*)strSubTitle;

@end

PlaceMark.m

#import "PlaceMark.h"

@implementation PlaceMark
@synthesize coordinate, titletext, subtitletext;

- (NSString *)subtitle{
	return subtitletext;
}
- (NSString *)title{
	return titletext;
}

-(void)setTitle:(NSString*)strTitle {
	self.titletext = strTitle;
}

-(void)setSubTitle:(NSString*)strSubTitle {
	self.subtitletext = strSubTitle;
}

-(id)initWithCoordinate:(CLLocationCoordinate2D) c{
	coordinate=c;
	return self;
}
@end

Adding a pin to your map

Firstly remember to add

#import "PlaceMark.h"

To your map controller, from the previous example inside the displayMap function under the region.center call

PlaceMark *addAnnotation = [[[PlaceMark alloc] initWithCoordinate:location] retain];
[addAnnotation setTitle:@"The Pin Title"];
[addAnnotation setSubTitle:@"The pin subtitle goes here"];

[mapView addAnnotation:addAnnotation];

Then create the following delegate method

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{
    MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"MyPin"];
    annView.animatesDrop=TRUE;
    annView.canShowCallout = YES;
	[annView setSelected:YES];
	annView.pinColor = MKPinAnnotationColorPurple;
    annView.calloutOffset = CGPointMake(-5, 5);
    return annView;
}

Voila you now have a pin dropped on the map and automatically selected. If you don’t want the title automatically displayed then change

[annView setSelected:YES];

The final part demonstrates how to create a mini map

How to embed a map on the iPhone

October 17th, 2009

In the first of a few posts I intend to make I’ll demonstrate some examples using the iPhone SDK.

This example will demonstrate how to embed a map, using the MKMapKit framework inside a UIView using the iPhone SDK. Note you must be using iPhone OS 3.0 or higher for this to work. The ultimate aim is to get a UIView that looks similar to this.

For this example I will assume you are already familiar with navigation and UIViews in the SDK (and so I won’t cover how to get the navigation bar at the top of this screenshot)

MKMapKit

Add the framework

First up you must go to the Frameworks folder of your XCode project and add the existing framework of MKMapKit.

Then inside the header file for your view add

#import <MapKit/MapKit.h>

Adding MapKit references to the header

Now we must add the mapKit instance to the header as well as the MapKit delegate

@interface MapKitViewController : UIViewController <MKMapViewDelegate> {
	MKMapView *mapView;
}
-(void)displayMap;

Initialize the Map

- (void)viewDidLoad {
	mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
	mapView.delegate=self;

	[self.view addSubview:mapView];
	[NSThread detachNewThreadSelector:@selector(displayMap) toTarget:self withObject:nil];
}

-(void)displayMap {
	MKCoordinateRegion region;
	MKCoordinateSpan span;
	span.latitudeDelta=0.2;
	span.longitudeDelta=0.2;

	CLLocationCoordinate2D location;
	location.latitude = -35;
	location.longitude = 146.2381;
	region.span=span;
	region.center=location;

	[mapView setRegion:region animated:TRUE];
	[mapView regionThatFits:region];
}

- (void)dealloc {
	[mapView release];
        [super dealloc];
}

This will give us a map fitting the screen with the region and zoom level set to best fit the coordinates given. Note that I have physically defined the coordinates here in this example, you can use something like the Google GeoCode API to convert addresses, etc to coordinates. I won’t cover that here.

Changing the map type

In the example above is a tool bar allowing you to switch map types

Let’s define the toolbar in the header file inside the @implementation

UISegmentedControl *buttonBarSegmentedControl;

Now inside the main code inside ViewDidLoad we add

buttonBarSegmentedControl = [[UISegmentedControl alloc] initWithItems:
	[NSArray arrayWithObjects:@"Standard", @"Satellite", @"Hybrid", nil]];
	[buttonBarSegmentedControl setFrame:CGRectMake(30, 10, 280-30, 30)];
        buttonBarSegmentedControl.selectedSegmentIndex = 0.0;	// start by showing the normal picker
	[buttonBarSegmentedControl addTarget:self action:@selector(toggleToolBarChange:) forControlEvents:UIControlEventValueChanged];
	buttonBarSegmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
	buttonBarSegmentedControl.backgroundColor = [UIColor clearColor];
	[buttonBarSegmentedControl setAlpha:0.8];

	[self.view addSubview:buttonBarSegmentedControl];

Then we must add the function for what happens when we tap on the tool bar

- (void)toggleToolBarChange:(id)sender
{
	UISegmentedControl *segControl = sender;

	switch (segControl.selectedSegmentIndex)
	{
		case 0:	// Map
		{
			[mapView setMapType:MKMapTypeStandard];
			break;
		}
		case 1: // Satellite
		{
			[mapView setMapType:MKMapTypeSatellite];
			break;
		}
		case 2: // Hybrid
		{
			[mapView setMapType:MKMapTypeHybrid];
			break;
		}
	}
}

In the next example, I’ll cover how to add the annotations (the small pins) to the map

The iTunes App Store review process

August 25th, 2009

Being a small fry iPhone app developer, that being I do so in my own spare time because I like developing apps for the iPhone, I’ve simply got to comment about the App Store review process as it stands.

I won’t make any individual comments about my personal rejections I’ve had except to say. I have had a number of rejections.

One update got rejected numerous times because apparently if you use a UIWebView where the user might be able to get to Google and therefore access pictures of <shock>naked people</shock> then you have to rate your App as 17+.  Let us ignore the fact that they could just close your app and go to mobile Safari shall we, it would be by far easier?

Another one I reason I’ve been rejected for features not doing something Apple think they should, yet that very same feature was actually in the version before it…. still on sale in the App Store.  Inconsistent reviewing doesn’t even begin to cover it!

So this leads me on to a blog post I read today from Joe Hewitt.  For anyone who doesn’t know who Joe is, he is the guy who wrote the amazing Facebook app for the iPhone.

I have only one major complaint with the App Store, and I can state it quite simply: the review process needs to be eliminated completely.

Does that sound scary to you, imagining a world in which any developer can just publish an app to your little touch screen computer without Apple’s saintly reviewers scrubbing it of all evil first? Well, it shouldn’t, because there is this thing called the World Wide Web which already works that way, and it has served millions and millions of people quite well for a long time now.

Go and read his excellent post now at what I totally agree with…. especially as I have one app that I originally submitted a month ago, awaiting approval!

UIActionSheet’s cancel button presses not detected properly

July 22nd, 2009

Last night I came across a really annoying bug with UIActionSheet when used in conjunction with a UITabBarController.

Basically there is a killer bug which means you can’t click the Cancel button on a UIActionSheet and forces the user to either close the App or use one of the destructive action buttons to get rid of the alert.

It appears to be a changed from OS <= 2.1 to >= 2.2. In iPhone OS 2.1 and earlier, the UIActionSheet comes up from the top of the tab bar, but in 2.2, it comes up from the bottom of the tab bar, and thus covers the tab view but the tab view still takes focus. If you try to press the cancel button below the top of the tab bar (barely visible through the semi-transparent UIActionSheet), the press does not register. If you click above the underlying tab bar, the press does work.

This is how you fix it.

1) In your AppDelegate.h function

+ (UITabBarController *)tabbarController;

Note that tabbarController should be different than your ACTUAL UITabBarController name.

Next edit your AppDelegate.m functiuon and above your @implementation

static AppDelegate *s_appdelegate = nil;

@implementation AppDelegate

Inside applicationDidFinishLaunching add

s_appdelegate = self;

At the bottom of AppDelegate.m before @end add

+ (UITabBarController *)tabbarController
{
	return s_appdelegate ? s_appdelegate.tabBarController : nil;
}

Finally in your child view in which you wish to display your UIActionSheet add

	UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Delete?"
		delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Yes" otherButtonTitles:nil, nil];

	UIView *viewBase = self.view;
	viewBase = [[AppDelegate tabbarController] view];
	[sheet showInView:viewBase];
	[sheet release];

iPhone push problems on unlocked phones

July 22nd, 2009

I read an article today about Push Notifications being displayed on the wrong people’s phones.

Now if the story is taken at face value then this would be a huge security flaw with the Apple Push Notification Service. However if you dig a little further you discover that it isn’t Apple’s problem at all, more the work of the unlocking community.

Now I’ll first start by explaining how the push notification service works. When you first load an application with push notification enabled, the application makes a call to the APNS (Apple Push Notification Service) servers. Those servers respond with a unique key for that device for push services. That unique key allows Apple to identify which device and which application to target for a push notification.

The App then communicates with the application’s author’s web servers and stores the key somewhere. The author’s servers then use that key to push a JSON encoded payload the APNS servers and the notification gets displayed on the user’s phone.

With me so far? So how does this break on unlocked iPhones?

Well unlocked / hacktivated phones haven’t actually been activated with Apple’s activations servers, they’ve simply been fooled into thinking that they have.

From what I’ve read about this situation is that the hacktivating / unlocking community have taken the key(s) from a properly activated device and put as part of the process.

So what is actually happening is that multiple phones are recieving legitimate push messages for the original key holder but not for them.

At present it would seem that push notifications simply won’t work on hacktivated / unlocked phones.

I guess the lesson is, if the software is written to prevent you from unlocking / by passing activation and then you do bypass it, then don’t expect everything to work properly!

How code push notifications on the iPhone 3.0

July 15th, 2009

I recently spent an entire evening trying to get this to work within an App and then post the message from my PHP application to Apple’s service.

First up you must create a CSR using your private key, you must have done this before to even be using the SDK so I’ll skip over it.

Login to the Apple Dev Center then to the iPhone Developer Program Portal.

Click on App IDs and add a New App ID.

  1. You need to create an App ID without wildcard in the Program Portal (that means one cert for one app.  They explain how to do this in the portal)
  2. Generate a certificate signing request from your Mac’s keychain and save to disk
  3. Upload the CertificateSigningRequest.certSigningRequest to the Program Portal
  4. Wait for the generation of cert (about 1 min). Download the certificate (aps_developer_identity.cer) from the Program Portal (and also the distribution certificate if you wish too, we’ll deal with just the developer identity for this)
  5. Keep the 2 files (steps 2 and 4) in a safe place. You might need the CertificateSigningRequest.certSigningRequest file to request a production cert in the future or renew it again.
  6. Import the aps_developer_identity.cer to the keychain. Then you have to export these new cert and the private key of this cert (not the public key) and saved as .p12 files.
  7. Then you use these commands to generate the cert and key in Mac’s Terminal for PEM format (Privacy Enhanced Mail Security Certificate)
    openssl pkcs12 -clcerts -nokeys -out certificate.pem -in cert.p12
    openssl pkcs12 -nocerts -out key.pem -in key.p12
  8. The certificate and private key files are used by your software to communicate with APNS
  9. You might want to remove the passphrase from the private key (you don’t have to do this but it makes it slightly more complicated in your code). To do this
    openssl rsa -in key.pem -out key.unencrypted.pem

    Then you’ll need to generate a single .pem file combining your certificate and key files

    cat certificate.pem key.unencrypted.pem > apnsdev.pem

So copy the generate apnsdev.pem file to your server and then you can setup your PHP application to push notifications to your iPhone.

$deviceToken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // masked for security reason
// Passphrase for your private key
// $pass = '';

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'apnsdev.pem');
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);

$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp) {
  print "Failed to connect $err $errstr\n";
}
else {
  print "Connection OK\n";
  $payload = '{"aps":{"badge":' . $number . '}}';
  $msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
  print "sending message :" . $payload . "\n";
  fwrite($fp, $msg);
  fclose($fp);
}

Note: if as a developer you install your app with your developer provision profile you’ll have to use the sandbox to communicate with your phone, you won’t be able to use the production push servers without the distribution profile

So that’s stage one, how to configure your web server with PHP to push to an iPhone, but how does the App actually register itself for push and secondly how do you get the device token to your server?

In XCode, in AppDelegate.m

- (void)applicationDidFinishLaunching:(UIApplication *)application {

 // This tells APNS that you wish to receive both Badge notifications and sound
 [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}

Then we can make two delegate functions in AppDelegate.m too

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  NSLog(@"deviceToken: %@", deviceToken);
  [self sendToMyServer:deviceToken];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  NSLog(@"Error in registration. Error: %@", error);
}

You’ll need to create a function for ’sendToMyServer’ and deal with the deviceToken, such as convert it to a string, etc. But essentially that is how you deal with push notifications on your iPhone!

Update: Oh and if you get the error

Warning: stream_socket_client() [function.stream-socket-client]: Unable to set local cert chain file `apnsdev.pem'; Check that your cafile/capath settings include details of your certificate and its issuer in /Library/WebServer/Documents/notifyTest/anps.php on line 33

or similar, the chances are you’ve exported the wrong certificate and/or key from your OS X Keychain

Some more iPhone SDK tips and tricks

April 8th, 2009

In working on an update to one of my applications on the Apple iPhone I thought I’d share another quick tip.

I found on a blog and it works brilliantly,  to make rounded corners on any UIImage.

ImageManipulator.h

@interface ImageManipulator : NSObject {
}
+(UIImage *)makeRoundCornerImage:(UIImage*)img :( int) cornerWidth :( int) cornerHeight;
@end

ImageManipulator.m

#import "ImageManipulator.h"

@implementation ImageManipulator

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight)
{
    float fw, fh;
    if (ovalWidth == 0 || ovalHeight == 0) {
        CGContextAddRect(context, rect);
        return;
    }
    CGContextSaveGState(context);
    CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
    CGContextScaleCTM (context, ovalWidth, ovalHeight);
    fw = CGRectGetWidth (rect) / ovalWidth;
    fh = CGRectGetHeight (rect) / ovalHeight;
    CGContextMoveToPoint(context, fw, fh/2);
    CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
    CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
    CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
    CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
    CGContextClosePath(context);
    CGContextRestoreGState(context);
}

+(UIImage *)makeRoundCornerImage : (UIImage*) img : (int) cornerWidth : (int) cornerHeight
{
	UIImage * newImage = nil;

	if( nil != img)
	{
		NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
		int w = img.size.width;
		int h = img.size.height;

		CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
		CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);

		CGContextBeginPath(context);
		CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
		addRoundedRectToPath(context, rect, cornerWidth, cornerHeight);
		CGContextClosePath(context);
		CGContextClip(context);

		CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);

		CGImageRef imageMasked = CGBitmapContextCreateImage(context);
		CGContextRelease(context);
		CGColorSpaceRelease(colorSpace);
		[img release];

		newImage = [[UIImage imageWithCGImage:imageMasked] retain];
		CGImageRelease(imageMasked);

		[pool release];
	}

    return newImage;
}

@end

Just call the static method makeRoundCornerImage and pass your image to have the image rounded off the way you want.

For example

UIImage *imageFromFile = [UIImage imageNamed:@"myimage.png"];
imageFromFile = [ImageManipulator makeRoundCornerImage:imageFromFile : 20 : 20];

Note that you do need the CoreGraphics framework for this to compile.

More information can be found at the blog post link above.

The second tip is to load a remote image over the web and display it in a UIImageView object.

Create the following method in your interface

-(UIImage*) newUIImageWithURLString:(NSString*)urlString
{
	return [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
}

Then call the following

UIImage *myImage = [self newUIImageWithURLString:@"http://url.to/image.jpg"];