Launching Safari From Your App

This is one of the simplest things you can do, launch the Safari mobile browser from inside your iOS App.

Here’s how we’d launch it for SitterSat.

SitterSat AppStore Icon

SitterSat AppStore Icon

Let’s say I have a button on my app. The button is linked through the interface builder to a method that handles the click titled LaunchSafariButtonClick.  If I have any typos, sorry, I typed it out by hand real quick, but you get the idea I’m sure.

- (IBAction)LaunchSafariButtonClick:(UIButton *) sender {
  [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.sittersat.com"]];
}

Yep, you can use a UIWebView to display the page inside your app, but this is a quick way to launch Safari outside of your app.

Teleporting a Kangaroo

scott sappenfield

Remember this guy? He’s our friendly kangaroo lying around in the sun.

Well, he’s bored sitting in isolation in my iPhone photo gallery and wants to go up to the web to be shared with everyone. I haven’t looked up the definition of teleportation, but I have to imagine there’s a sender and a receiver.  So in this case, he’s going to be teleported from my iPhone (that’s the transmission sender) all the way up to the world wide web (that’s the transmission receiver).

Disclaimer

No actual animals have been harmed for the purposes of this post. Sorry, that’s another lame attempt at humor. It’s tough for me to make the nuts and bolts of technical stuff entertaining, but I try.

The Nuts and Bolts

Let’s see what’s going on with the code ( I took some code out, moved some other code around and added some commentary just for simplicity and clarity.)  In my actual code, there are things you don’t need to concern yourself with.  For instance, I start spinning an animator.  While that’s useful, you don’t need those details here.

The Sender

My iPhone.  Let’s send it on up to the receiver.  This method is dispatched in a separate thread, so keep that in mind.  If you don’t do that, make sure you do an asynch request so as to not tie things up unnecessarily.

(void)uploadMyNewPictureToWeb {

        //your web service that's on the lookout for Mr. Kangaroo
        NSMutableString *customWebPage = [NSMutableString stringWithString:@"your web service URL would go here"];
        NSURL *aUrl = [NSURL URLWithString:customWebPage];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:aUrl];

        //this is your POST, PUT, whatever restful protocol you support
        [request setHTTPMethod:@"POST"];

        //set the content type
        //the spec calls for a unique string of characters as a separator, so use something random, but unique
        //also, I can't stress enough how important it is to get all the \r\n's correct
        NSString *boundary = @"DK39FJK4589FDKJSW893JKLR89DFJK238934IOSO";
        NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
        [request setValue:contentType forHTTPHeaderField:@"Content-Type"];

        //body
        NSMutableData *body = [NSMutableData data];

        //add all the textual params first (I'm going to assume you have some things to transmit other than just a photo
        //let's call them param1 and param2
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"param1"] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", _param1] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"param2"] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", _param2] dataUsingEncoding:NSUTF8StringEncoding]];

        //now add the image data, remember in the original post, I used a button, you could get this from local documents storage or wherever
        NSData *imageData = UIImagePNGRepresentation(_profilePictureButton.imageView.image);
        if (imageData) {
            [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"mrkangaroo.png\"\r\n", @"mrkangaroo"] dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithFormat:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[NSData dataWithData:imageData]];
            [body appendData:[[NSString stringWithFormat:@"%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        }
        [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

        //set the body to the request
        [request setHTTPBody:body];

        //set the content length
        NSString *postLength = [NSString stringWithFormat:@"%d", [body length]];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];

        NSURLResponse *response;
        NSError *error;

        //send it and do something with the response...response parsing is left off here for simplicity
        NSData *myJSONNetworkData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

        //parsing response...
}

The Receiver

A web service keeping an eye out for Mr. Kangaroo.

You probably don’t need an example, but sometimes it helps to see how to handle things on the receiving end.  Whatever you use (ROR, PHP, JSP) on the receiving end will be able to easily parse this data.  SitterSat.com was written in ASP.Net/VB on the server-side, so here it is in that language. Again, I’ve removed a lot because you don’t need all that, just the relevant pieces.

//ASP.Net libraries have many useful collection for you, one being files in the request object
Dim fileCollection As HttpFileCollection = Request.Files

//Code works, but is simplified here for you, meaning you'll want more robust error handling and logging
For Each uploadedFileName In fileCollection
  Dim uploadedFile As HttpPostedFile = fileCollection(uploadedFileName)
  If Not uploadedFile Is Nothing Then
    If (uploadedFile.ContentLength > 0) Then
        //Save it somewhere on the server, whatever path conventions you have
        //also, this doesn't save it with a file extension, but you could that just the same
        uploadedFile.SaveAs(Server.MapPath("~/Pictures/mrkangaroo"))
    End If
  End If
Next

I hope this will help you out should you ever need to get into the business of transporting. Best of luck, let me know if I can help with anything.  You know, now that I’m thinking about it, I went to all this trouble, why didn’t I just tweet or FB him?  Oh well.

Cameras Take Pictures

The title of this post is very underwhelming, I know.  But it’s true.  Cameras take pictures.

That you already knew.  And it seems that everyone also knows how to use their trusty Nikon, but since having written the post on how to create your own iOS App, I’ve been asked how did I use the camera on the phone for the SitterSat app?  Here’s how, let me walk you through it.  It’s delightfully easy.

scott sappenfield

Kangaroo lounging around (makes for a nice profile pic)

The Setup

Add a button to your View.  I called mine ProfilePictureButton synthesized to _profilePictureButton (that’s my preferred naming convention in Objective-C).  Why a button?  You can use setImage on a UIButton which will give you all the nice and useful features of a button while at the same time show your photo.

//in your header

@property (weak, nonatomic) IBOutlet UIButton *ProfilePictureButton;

//in your implementation

@synthesize ProfilePictureButton = _profilePictureButton;

//don't forget to unload it - I'm using automatic reference counting (ARC) so no need for me to retain/release

[self setProfilePictureButton:nil];
scott sappenfield

UIButton uses setImage to display your photo

The Implementation

1. Add an implementation method that actually does something.  Remember, I created a button, so let’s associate a method to do something when it’s touched by the user.  Glance over the method and I’ll add some detail to the finer points in the next section.

- (IBAction)ProfilePictureButtonClick:(UIButton *)sender {

    //If the device has a camera, allow a choice via an action sheet
    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
        NSLog(@"Device has a camera, will show ActionSheet for a choice");
        UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle: nil
                                                                 delegate: self
                                                        cancelButtonTitle: @"Cancel"
                                                   destructiveButtonTitle: nil
                                                        otherButtonTitles: @"Take Photo",
                                      @"Choose Existing Photo", nil];
        [actionSheet showFromRect: _profilePictureButton.frame inView: _profilePictureButton.superview animated: YES];
    } else {
        NSLog(@"Device does not have a camera, will show gallery");
        //otherwise, if it doesn't have a camera, just go ahead and show the picker
        UIImagePickerController *picker = [[UIImagePickerController alloc] init];
        picker.delegate = self;
        picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        picker.allowsEditing = YES;
        [self presentModalViewController:picker animated:YES];
    }

}

2. Ok, so in the ProfilePictureButtonClick method, you’ll see references to UIImagePickerController as well as UIActionSheet.  You’re going to want to tell the compiler about those interfaces, so modify your view controller to include those, as given here with this snippet:

UIViewController <UIImagePickerControllerDelegate, UIActionSheetDelegate>

The reason you need UIActionSheetDelegate is because I chose to use an action sheet when prompting the user.  Specifically, I ask the user with a modal dialog whether or not to use the camera or the gallery on the phone.  You need to ask, if you don’t, get ready for a big rejection on submission to the AppStore. That said, if there is no camera available, you can simply default right to the gallery.  Just think of it a bit like HTML5 modernization techniques where you’re inspecting the devices’ capabilities.

scott sappenfield

Action sheet presents the user with an option

Also notice, for the action sheet, I’ve made the delegate that’s going to handle the user’s responses to the action sheet, self.  Ok, so that’s this view controller.  We’ve already informed the compiler that we’re going to implement methods for this purpose, so let’s do so now.

3. Which button on the action sheet was touched

-(void) actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {

    //Show the picker but only from whatever choice the user made
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.allowsEditing = YES;
    NSString *buttonTitleAtButtonIndex = [actionSheet buttonTitleAtIndex:buttonIndex];
    if ([buttonTitleAtButtonIndex isEqualToString:@"Take Photo"]) {
        // take photo...
        NSLog(@"User decided to take a photo");
        picker.sourceType = UIImagePickerControllerSourceTypeCamera;
        [self presentModalViewController:picker animated:YES];
    } else if ([buttonTitleAtButtonIndex isEqualToString:@"Choose Existing Photo"]) {
        // choose existing photo...
        NSLog(@"User decided to pick from the gallery");
        picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        [self presentModalViewController:picker animated:YES];
    }

}

4. We’ve also kindly informed the compiler that we will be doing stuff with the actual image picker.  So let’s do that too.  First, let’s create a method for when the Cancel button is touched.

- (void)imagePickerControllerDidCancel:(UIImagePickerController *) Picker {

    NSLog(@"imagePickerControllerDidCancel called");
    [Picker dismissModalViewControllerAnimated:YES];

}

Now all you have to do is implement the method to actually do something with the photo, taken with the camera or picked from the gallery. That’s up to you.

- (void)imagePickerController:(UIImagePickerController *) Picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

}

That’s all folks! :>

But if it helps, let me give you my implementation method so you can use some of the code if you wish. You’ll see a couple of things, I put in some comments and extra code (that compiles fine) for you that aren’t in my code per se, but just to help you walk along.

- (void)imagePickerController:(UIImagePickerController *) Picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    NSLog(@"didFinishPickingMediaWithInfo called");
    UIImage *selectedImage = [info objectForKey:UIImagePickerControllerEditedImage];

    //allows editing is TRUE, if you will notice in my picker alloc.
    //scale box down to 200x200 - reference above kangaroo cropper
    float actualHeight = 200.0;
    float actualWidth = 200.0;
    CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
    UIGraphicsBeginImageContext(rect.size);
    [selectedImage drawInRect:rect];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    //dismiss the picker
    [Picker dismissModalViewControllerAnimated:YES];

    //get a path to the local documents directory
    NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *pathToProfilePicture = [NSString pathWithComponents:[NSArray arrayWithObjects:documentsDirectory, @"userprofilepicture", nil]];

    //delete the local profile picture, if one exists already
    BOOL profilePictureFileExists = [[NSFileManager defaultManager] fileExistsAtPath:pathToProfilePicture];
    if (profilePictureFileExists) {
        NSLog(@"User profile picture exists, we will delete it now");
        [[NSFileManager defaultManager] removeItemAtPath:pathToProfilePicture error:nil];
    }

    //now create the file
    BOOL createdFileOk = [[NSFileManager defaultManager] createFileAtPath:pathToProfilePicture contents:nil attributes:nil];
    if (!createdFileOk) {
        NSLog(@"Error creating profile picture image file to write to on disk %@", pathToProfilePicture);
    } else {
        NSLog(@"Writing profile picture image file to disk...");
        NSFileHandle* profilePictureHandle = [NSFileHandle fileHandleForWritingAtPath:pathToProfilePicture];
        [profilePictureHandle writeData:UIImagePNGRepresentation(croppedImage)];
        [profilePictureHandle closeFile];
        _pictureProfileDirtyBit = @"N";
        NSLog(@"Writing profile picture complete");

        //show the image you just saved
        [_profilePictureButton setImage:croppedImage forState:UIControlStateNormal];

        //now asynchronously bump this picture up to the web
        //I might give you the guts of how to upload something like this to a web service listening for such information, but not in this post
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
        dispatch_async(queue, ^{[self uploadMyNewPictureToWeb];});

    }

}

I’d love to know if there’s a better way to accomplish this or if you used a similar approach for something of your own.