rss
twitter
  •  

Creating a mini map using MapKit on the iPhone

| Posted in iPhone |

1

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

| Posted in iPhone |

2

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

| Posted in iPhone |

1

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

| Posted in iPhone |

0

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

| Posted in iPhone |

1

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

| Posted in iPhone |

0

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

| Posted in iPhone |

0

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

| Posted in iPhone |

0

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"];

.tel goes live

| Posted in iPhone |

0

media_right_3Well I’ve just got my .tel domain http://richardhyland.tel

Time will tell if it’s any good or going to work, however I’ve actually got a .tel iPhone management tool awaiting approval from Apple.