Saving UIImage to the photo album.

The simplest way to save a UIImage to the photo album is with the UIImageWriteToSavedPhotosAlbum() function. The simplest way is by specifying the UIImage, and none of the other parameters:

    UIImageWriteToSavedPhotosAlbum(image, nil,nil, nil);

However, this will save the image as a jpg, which is not always ideal. If you want to save it as PNG, you have to first encode the image as a PNG, then make a new image based on that PNG so that the PNG data is attached to it, and then save the new image:

    NSData *data=UIImagePNGRepresentation(image);
    image=[UIImage imageWithData:data];
    UIImageWriteToSavedPhotosAlbum(image, nil,nil, nil);

You can also force JPEG format (with your own compression quality) by doing the same thing, but with the UIImageJPEGRepresentation() call:

    NSData *data=UIImageJPEGRepresentation(image,1.0); // best quality
    image=[UIImage imageWithData:data];
    UIImageWriteToSavedPhotosAlbum(image, nil,nil, nil);

Making your iOS OpenGLES view transparent

At some point you may want to make your OpenGL ES view transparent, so you can display something else behind it. (Maybe a camera view?) For this article, I’m going to assume you are using a Game project template from XCode 6.

In your GameViewController.m’s viewDidLoad function, just before the ‘[self setupGL];’ call, add the following lines:

    view.backgroundColor=[UIColor clearColor];
    
    CAEAGLLayer *eaglLayer=(CAEAGLLayer*)self.view.layer;
    eaglLayer.opaque=NO;

Also, in the glkView:drawInRect function, change the glClearColor line to be all zeroes:

    glClearColor(0.00f, 0.00f, 0.00f, 0.0f);

That should be it.

Fixing the status bar visible after photo roll showing bug on iOS7/iPad

In iOS7 disabling the status bar is a bit more complex than it was in iOS6. (We have to add a row call it “View controller-based status bar appearance” and set it to boolean NO in the Target Info properties tab.)

Unfortunately, there’s also a bug on the iPad (up to 7.1 as of this writing) that makes the status bar visible again after the photo roll is shown. Luckily, there’s a pretty easy fix.

First, make sure your UIViewController subclass (the one that is already a UIImagePickerControllerDelegate) is a UINavigationControllerDelegate as well. (It should be anyway to get rid of the warnings in XCode.) Then add the following UINavigationControllerDelegate function to that subclass:

- (void)navigationController:(UINavigationController *)navigationController
      willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    [[UIApplication sharedApplication] setStatusBarHidden:YES];
}

That’s it!

Debugging no-touch zones

When converting some iOS apps from iOS6 to iOS7, I came across a very strange bug. I had an app with tons of views and view controllers, and on the right side of the screen, touches wouldn’t be recognized. (For buttons, gestures, scrolling, etc.) It was quite obvious that one of the views frame’s wasn’t covering that part of the screen. The easy way to figure out which view it is is to add:

    self.view.clipsToBounds=true;

inside each viewDidLoad, verify that the dead zone is no longer visible, and then slowly remove the clipsToBounds calls one at a time until the dead zone is visible again, and you’ve found the offending view! Then take whatever steps you need to adjust that view properly.

In my case, a view had a frame (0,0+768,1024) for a landscape view. Changing this to (0,0+1024,768) resolved the issue. Oddly enough, either sized frame didn’t cause iOS6 to have issues, just iOS7.

Hiding the navigation bar shadows in iOS7

I had a doozy of a time trying to hide the tiny little one pixel high grey shadows in the navigation and tab bars. Google had a lot of suggestions, but none of them worked until I came up with a combination of a few that did the trick. In your application:didFinishLaunchingWithOptions: functions, add the following:

   [[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];
   [[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];

This removes the shadow for all navigation bars and tab bars, so if you want a different behavior this probably won’t work.

Also make sure your navigation bar and tab bar don’t have translucency set. (There’s a checkbox in Interface Builder.)

Adding Push notifications to your iOS app

At some point you’ll probably want to add Push notifications to your iOS app, be it for app updates, messages, etc. It’s actually pretty easy to do.

Creating/Updating the App ID For Push Notification

First, go into the provisioning portal, and click on ‘App IDs’ on the left under ‘Identifiers’. On the upper right corner click on the ‘+’ sign to get to the ‘Registering an AppID’ screen, fill in the App Id Description and App ID Suffix, using an Explicit App ID. (The app ID cannot have a wildcard (*) in its Bundle ID.) Under App Services, make sure ‘Push Notifications’ is checked. Click Continue, and on the ‘Confirm your App ID’ screen, click on ‘Submit’. Your new App ID should now be shown in the App ID’s list.

Click on the new App ID in the list. The ‘Push Notifications’ line should be ‘Configurable’. Click ‘Edit’ to get to the ‘iOS App ID Settings’ screen. Scroll down to ‘Push Notifications’ and click ‘Create Certificate’ under the appropriate section.

You’ll now see the ‘About Creating a Certificate Signing Request (CSR)’ screen. It will tell you to launch Keychain Access under Applications/Utilities, and select Keychain Access->Certificate Assistant->Request a Certificate from a Certificate Authority. You then fill in your email address and create a name for your private key. In the Request is group, you select ‘Save to disk’ option, and click continue. You’ll be prompted for the location and name of your CertificateSigningRequest.

Once you’ve done as requested, hit ‘Continue’ on the ‘About Creating a Certificate Signing Request (CSR)’ screen, and you’ll be in the ‘Generate your certificate’ screen. Click on ‘Choose File’ and select the CertificateSigningRequest file you just created in Keychain Access and click Generate.

After up to a minute, you’ll get the message saying that your certificate is ready. Click on ‘Download’. When you’ve downloaded it, click ‘Continue’ and you’ll get back to the Configure App ID screen. Hit ‘Done’ at the bottom to save your new push notification settings.

Double click on the APNs SSL Certificate to install it into your keychain. Once its installed, export it as ‘PushCertificate.p12’. (No password.) We’ll need it later.

Creating/Updating a provisioning profile

You can’t use your wildcard provisioning profile for push notifications; you have to use one specifically for push notifications for the given App ID. Once again, go into your provisioning portal to start.

Once there, click on ‘Provisioning’ on the left hand side. If you have a provisioning profile with the same app ID as above, Edit/Modify it; if not, create a new profile with that app ID, calling it something like ‘MyApp Push’. Once you’re done, make sure to Download the new provioning profile and select it as the current development code signing identity. If you don’t do this, you’ll get “no valid ‘aps-environment’ entitlement string found for application” messages when you try to run it.

Registering for notifications

Now that you’ve got the correct provisioning profile installed and in use, you’ll need to add code to register your app for push notifications.

In your app delegate’s application:didFinishLaunching or application:didFinishLuanchingWithOptions code, add the following code:

   [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
               UIRemoteNotificationTypeBadge |
               UIRemoteNotificationTypeAlert |
               UIRemoteNotificationTypeSound];

Remove any notification types you don’t want to receive.

You’ll also need to add Push Notification callback code in your app delegate:

#pragma mark = Push Notification code

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
   NSLog(@"did register, deviceToken: %d bytes, %@", devToken.length,devToken);

   // Add code here to send the device token to your server, probably converting to hex first
   [self sendDevToken:devToken toURL:@"http://www.example.com/register.php"];
}

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

   // deal with the error here
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
   NSLog(@"Got notification: %@",userInfo);
   
   // notify the user, etc about the notification just received
}

The easiest way to send a device token to your server to register your device is to convert the devToken to a hex string, and pass it as a parameter to a php script on a website. (ie http://www.example.com/register.php?key=) It would also be a good idea to spawn a thread to do this so you’re not blocking the user input while registering.

Your app should now be registering for notifications with Apple and registered their deviceToken on your server.

Server side registration

On your PHP server, you’ll want to receive the deviceToken and store it for later use. (When you want to notify the user of something.) We can start with something like this in our register.php file to make sure we’re getting registrations:

<?php

$key=$_GET["key"];

// We should store in db for later use, but we'll just log it for now.
$fp=fopen("register.log","a");
fwrite($fp,date("YmdHis")." $key\n");
fclose($fp);

?>

Run the app to verify registration

Now that you’ve set your app up to register, and your server set up to receive the registrations, try it out, and if all’s well, you should get an entry in a ‘register.log’ file. Make sure to accept push notifications!

Pushing notifications from your server

Now that we’re registered, we need to get our server to talk to apple’s server to send our registered devices push notifications. This is where the PushCertificate.p12 file we created early comes into play. On your linux php server, we need to create a cert/key file that’s used to communicate securely with the apple push server. Upload the PushCertificate.p12 file to your server and create the ck.pem file as follows:

$ openssl pkcs12 -clcerts -nokeys -out cert.pem -in PushCertificate.p12 
Enter Import Password:
MAC verified OK

$ openssl pkcs12 -nocerts -out key.pem -in PushCertificate.p12 
Enter Import Password:
MAC verified OK
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

$ cat cert.pem key.pem > ck.pem

Make sure that you enter the same passphrase both times. (This will be used in the push code below.)

Now that we have a cert/key file, and a devToken, we can send our device a push notification with the following (replace with the passphrase you used above.):

<?php
$msg="<Your notification message>";
$pushToken= "<your captured hex push token>";
$pushToken=pack("H*",$pushToken);

send_apns($pushToken,$msg,1,'');

function send_apns($token,$message,$badge,$sound) {
   // Construct the notification payload
   $body = array();
   $body['aps'] = array('alert' => $message);
   if ($badge) $body['aps']['badge'] = $badge;
   if ($sound) $body['aps']['sound'] = $sound;

   $ctx = stream_context_create();
   stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
   stream_context_set_option ($ctx, 'ssl', 'passphrase', '<password>');
   $fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
   // for production change the server to ssl://gateway.push.apple.com:2195
   if (!$fp) {
      print "Failed to connect $err $errstr\n";
      return -1;
   }

   $payload = json_encode($body);
   $msg = chr(0) . pack("n",32) . $token . pack("n",strlen($payload)) . $payload;
   fwrite($fp, $msg);
   fclose($fp);

   return 0;
}
?>

At this point you should hopefully have a push notification sent to your device. As noted in the code, change the server as appropriate if you’re in production.

Creating, printing and emailing high resolution PDF’s in iOS

Many kinds of business software require some form of PDF output. The easiest way in iOS to create PDF’s is using Interface Builder and the UIKit.

Generate a PDF using UIKit

First, create a blank new empty user interface file in XCode. I’ll assume you called it PDFView.xib. Make whichever view controller will be generating the PDF the owner.

Add the main pdf view, and make it 612×792, which Apple says is the standard size for a full 8.5×11 sheet of paper. (You’ll need it a standard size if you want to print, since AirPrint doesn’t allow you to shrink larger pdf’s to fit, instead cropping off the sides that are too big or printing the page tiled across multiple pages.) Assign the main view to an UIView IBOutlet in your owner view controller; I’ll assume its called PDFView1.

In the main pdf view, lay out all your UI elements as UIViews (to group), UIImageViews, and UILabels. You can add other UI elements if you want, but most standard documents can be created with only those two.

You can add more views to the file if you want to add more pages to your PDF.

When you want to create your PDF, you would do something like this:

- (NSData*)generatePDF {
   NSMutableData * pdfData=[NSMutableData data];

   [[NSBundle mainBundle] loadNibNamed:@"PDFView" owner:self options:nil];

   // by default, the UIKit will create a 612x792 page size (8.5 x 11 inches)
   // if you pass in CGRectZero for the size
   UIGraphicsBeginPDFContextToData(pdfData, CGRectZero,nil);
   CGContextRef pdfContext=UIGraphicsGetCurrentContext();

   // repeat the code between the lines for each pdf page you want to output
   // ======================================================================
   UIGraphicsBeginPDFPage();

   // add code to update the UI elements in the first page here
	
   // use the currently being outputed view's layer here	
   [self.PDFView1.layer renderInContext:pdfContext];

   // end repeat code
   // ======================================================================

   // finally end the PDF context.
   UIGraphicsEndPDFContext();

   // and return the PDF data.
   return pdfData;
}

Pretty simple eh?

Saving/Printing/Emailing the PDF.

Once you’ve generated the PDF, its easy to save/print/email.

Saving is just a matter of writing it to a file:

- (BOOL)savePDF:(NSData*)pdf toFile:(NSString*)filePath {
   if(![data writeToFile:filePath atomically:NO]){
      NSLog(@"Failed to pdf to file '%@'", filePath);
      return NO;
   }
   return YES;
}

Printing is not much harder. We simply grab the shared print controller, set up the orientation, job name, etc, set the printer’s printingItem to our PDF data, and present the printer controller to the user, which lets them choose which printer to use. iOS takes care of the rest.

- (void)printPDF:(NSData*)pdfData {
   UIPrintInteractionController *printer=[UIPrintInteractionController sharedPrintController];
   UIPrintInfo *info = [UIPrintInfo printInfo];
   info.orientation = UIPrintInfoOrientationPortrait;
   info.outputType = UIPrintInfoOutputGeneral;
   info.jobName=@"CadabraCorp.pdf";
   info.duplex=UIPrintInfoDuplexLongEdge;
   printer.printInfo = info;
   printer.showsPageRange=YES;
   printer.printingItem=pdfData;
		
   UIPrintInteractionCompletionHandler completionHandler =
      ^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
         if (!completed && error)
         NSLog(@"FAILED! error = %@",[error localizedDescription]);
      };
   [printer presentAnimated:YES completionHandler:completionHandler];
}

Emailing is similarly simple. We just create a new MFMailComposeViewController, add the data as a pdf attachment, and present it to the user to fill in email address, etc. (We could put in defaults before we present it, but I leave that to your imagination.):

- (void)emailPDF:(NSData*)pdfData {
   MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];

   picker.mailComposeDelegate = self;
   [picker setSubject:@"Sending PDF"];
   [picker addAttachmentData:pdfData mimeType:@"application/pdf" 
                    fileName:[NSString stringWithFormat:@"CadabraCorp.pdf"]];
   [picker setMessageBody:@"Here's the PDF you wanted." isHTML:YES];

   [self presentModalViewController:picker animated:YES];
   [picker release];
}

#pragma mark = MFMailComposeViewControllerDelegate function

- (void)mailComposeController:(MFMailComposeViewController*)controller 
          didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
   [self dismissModalViewControllerAnimated:YES];
   if (result==MessageComposeResultSent) {
      NSLog(@"PDF sent");
   } else {
      NSLog(@"PDF send error %@",error);
   }
}

Why does it look so pixelated?

Unfortunately, using the standard way of creating PDF’s with UIKit results in a bit of a problem… By default, UIKit uses 72dpi, and that leaves quite a bit of it looking pixelated. The only solution I’ve found is to make the View’s larger (I usually do 4x larger to make the math easy) and then shrink it when generating the PDF. This results in a higher resolution PDF that is still a full page and prints properly at 100%.

First, resize each UI element by multiplying its x/y/width/height by 4. Any fonts should have their point sizes multiplied by 4 as well. You may have to create new images for the UIImageView’s you use.

Next, modify the creation of the output PDF to scale the layer’s:

- (NSData*)generatePDF {
   NSMutableData * pdfData=[NSMutableData data];

   [[NSBundle mainBundle] loadNibNamed:@"PDFView" owner:self options:nil];

   // by default, the UIKit will create a 612x792 page size (8.5 x 11 inches)
   // if you pass in CGRectZero for the size
   UIGraphicsBeginPDFContextToData(pdfData, CGRectZero,nil);
   CGContextRef pdfContext=UIGraphicsGetCurrentContext();

   // repeat the code between the lines for each pdf page you want to output
   // ======================================================================
   UIGraphicsBeginPDFPage();

   // add code to update the UI elements in the first page here
	
   CGContextSaveGState(pdfContext);
   CGContextConcatCTM(pdfContext,CGAffineTransformMakeScale(.25, .25));

   // use the currently being outputed view's layer here	
   [self.PDFView1.layer renderInContext:pdfContext];

   CGContextRestoreGState(pdfContext);

   // end repeat code
   // ======================================================================

   // finally end the PDF context.
   UIGraphicsEndPDFContext();

   // and return the PDF data.
   return pdfData;
}

That’s it! The end result is PDF’s of exactly the same size (8.5×11) but with higher resolution text and images.

Grabbing a web file’s last modified date

If you rely on updating files stored on a server, you want a way to find out when a file has been updated on that server, so you can download the latest version.

The easiest way I’ve found is using NSURLConnection’s sendSynchronousRequest:returningResponse:error method, but using a HEAD http method instead of GET. Here’s a simple example:

+ (NSDate*)getFileDate:(NSString*)httpFilePath {
    NSURL *url=[NSURL URLWithString:httpFilePath];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    [request setHTTPMethod:@"HEAD"];
    NSHTTPURLResponse *response;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
    if (response==nil) return nil;
	
    NSDate *lastModifiedDate=nil;
    NSString *lastModified=[[response allHeaderFields] objectForKey:@"Last-Modified"];
    @try {
        NSDateFormatter *df = [[[NSDateFormatter alloc] init] autorelease];
        df.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
        df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
        lastModifiedDate = [df dateFromString:lastModified];
    }
    @catch (NSException * e) {
        NSLog(@"Error formatting Last-Modified date: %@ (%@)", lastModified, [e description]);
    }
    return lastModifiedDate;
}

For completeness sake, to grab a file, we’d use code like this:

+ (NSData*)getFileData:(NSString*)httpFilePath {
    NSURL *url=[NSURL URLWithString:httpFilePath];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    [request setHTTPMethod:@"GET"];
    NSHTTPURLResponse *response;
    NSData *result=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
    if (response==nil || response.statusCode>400) return nil;

    return result;
}

You can do something similar asynchronously, but its a bit more complicated.