Do you have an idea for an app but lack the programming knowledge to begin building it? In this weekly blog series, How to Unleash Your Inner App Developer, I will take you, the non-programmer, step by step through the process of creating apps for the iPhone, iPod touch, and iPad. Join me each week on this adventure, and you will experience how fun turning your ideas into reality can be! This is Part 11 of the series. If you're just getting started now, check out the beginning of the series here. (This post has been updated to iOS 7.1)
Now that you have some of the basics of Objective-C programming under your belt, it's time to dive into some deeper coding territory. However, first let's talk about the homework assignment I gave you last week. I asked you to enhance iAppsReview so that it pulled the name of the app from the Write Review scene's text field.
This required creating an outlet for the text field and using the NSString class to append two strings together. For a video demonstrating how to do this, check out this link.
To get the latest version of iAppsReview, including my previous post's homework assignment, select this link.
The iOS Photo Library
Apple allows the custom apps you create to access the Photo Library on an iOS device. This allows you to add some great functionality to your apps. Apple makes it easy to do so by providing a pre-built user interface allowing users to navigate their photos and select an image. Our goal in this post is to display an image picker when the user taps the image view with the Add Image placeholder (Figure 1), let them select an image from the photo album, and display a thumbnail of the image in the image view control to the right of the button.
|Figure 1 - The Photo Album button in the bottom-left corner of the Write Review scene|
Adding Photos to the Simulator
By default, the iOS Simulator doesn't have any photos in its Photo Library, which we need for this demo. Fortunately, it's easy to add images as outlined in these steps:
- In the Xcode menu, select Xcode > Open Developer Tool > iOS Simulator;
- Download the Doodle Jump app images I have created for you from this link. This automatically decompresses the DoodleJump folder into your Mac's Downloads folder;
- Open Finder and Navigate to your Downloads folder. Expand the DoodleJump folder and you will see the DoodleJump1.PNG and DoodleJump2.PNG files;
- Drag the DoodleJump1.PNG file from the Finder window to the Simulator as shown in Figure 2, and then release your mouse button.
|Figure 2 - Dragging an image to the Simulator adds it to the Simulator's Photo Library.|
When you do this, Safari is launched in the Simulator, and the image is displayed in the browser window as shown in Figure 3;
|Figure 3 - The image is displayed in Safari.|
- Next, click on the image and hold your mouse button down until a panel slides up from the bottom and then click the Save Image button.
- Now drag and drop the DoodleJump2.PNG from Finder onto the Simulator and repeat the same actions in the previous step to save the image.
That's it! Now you have two images in the iPhone Simulator you can access from the iAppsReview app. If you'd like, you can click the Simulator Home button, launch the Photos app, and check out the two newly added images.
Creating an Action Method and Outlet
In order to display the image picker view that allows the user to select an image from the Photo Album and store it in the image view, you need to do the following:
- Make the image view control interactive
- Add a Tap Gesture Recognizer control to the image view
- Create an accessPhotoLibrary: action method for the Tap Gesture Recognizer;
- Create an outlet named imgThumbNail for the image view.
Here are the detailed steps:
- Open the iAppsReview project in Xcode.
- Click on the image view (with the Add Image placeholder) in the Write Review scene to select it, and then go to the Attributes Inspector and select the User Interaction Enabled check box (Figure 4).
|Figure 4 - Select the User Interaction Enabled check box.|
- Unfortunately, image views don't have an event that gets fired when you tap on them. Here is where gesture recognizers come to the rescue. Drag a Tap Gesture Recognizer from the Object Library and drop it on the image view as shown in Figure 5.
|Figure 5 - Drag and drop a Tap Gesture Recognizer on the image view.|
This adds a second gesture recognizer to the dock below the Write Review Scene.
- Since we need to add a new action method to the scene's view controller, let's display the Assistant Editor. To do this, click the center button in the Editor button group at the top right of the Xcode window. This should automatically display the WriteReviewController.m file in the Assistant Editor window. If it doesn't, select Automatic > WriteReviewViewController.m from the navigation bar at the top of the Assistant Editor.
- Click on the gesture recognizer on the right to select it, and then go to the Connections Inspector (the first button on the right in the Inspectors toolbar.) In the Sent Actions section, click the connection well to the right of the selector action and drag down to the WriteReviewViewController.m file just below the sender: action method as shown in Figure 6.
|Figure 6 - Create a connection from the gesture recognizer.|
Release the mouse button and you will see a create connection popup (Figure 7). In the Name text field, enter accessPhotoLibrary and then click Connect.
|Figure 7 - Create a new action method named accessPhotoLibrary.|
This creates a new accessPhotoLibrary: action method declaration at the top of the code file. We'll come back and implement this method in just a bit, but first, let's create an outlet for the image view.
Creating an Outlet for the Image View
- In the Write Review scene, click on the image view. Next, go to the Connections Inspector, click the connection well to the right of New Referencing Outlet and drag down into the WriteReviewViewController.m file just above the tvwReview property as shown in Figure 8.
|Figure 8 - Create an outlet for the image view.|
- In the Create Outlete popup, enter imgThumbNail and click Connect. This creates a new outlet property as shown in Figure 9.
|Figure 9 - The new imgThumbnail outlet property|
Implementing the accessPhotoLibrary: Method
Now you're ready to add implementation code to the accessPhotoLibrary: method:
- Close the Assistant Editor by clicking the button on the left button in the Editor button group at the top right of the Xcode window;
- In the Project Navigator, click the WriteReviewViewController.m implementation file and scroll down to the bottom of the code file to see the accessPhotoLibrary: method;
- Add the code shown in Figure 10, and then we'll discuss what the code does (based on my previous posts, I'm assuming you know the basics of how to type code into a method. Make sure you allow Xcode's Code Completion to automatically complete your code rather than typing in every character yourself.)
|Figure 10 - accessPhotoLibrary: implementation code|
The first line of code creates an image picker controller object from the UIImagePickerController class. In the second line of code, the image picker controller object's sourceType property is set. The third line of code displays the image picker controller.
Let's take a closer look at the second line of code. Whenever you set the value of a property you're not familiar with, it's a good idea to read Apple's documentation to see how the property is used. Figure 11 contains the documentation for the sourceType property.
|Figure 11 - Apple's documentation for the sourceType property of the UIImagePickerController|
The documentation specifies that the sourceType property holds a value of the type UIImagePickerControllerSourceType, and it refers to the type as an enumeration. We'll find out what that is in just a moment.
If you click on the UIImagePickerControllerSourceType link, it takes you to the documentation shown in Figure 12.
|Figure 12 - The UIImagePickerControllerSourceType enumeration|
An enumeration is a group of related constant values. In this case, the UIImagePickerControllerSourceType enumeration defines the valid values that can be stored in the sourceType property. Enumerations come in handy in cases like this when there are a finite number of valid values that can be stored in a property. Unlike string variables where you can store any string of characters, or integer variables where you can store any valid integer, enumerations limit the valid values to a finite set.
Reading through the documentation, we can determine that the value we want is UIImagePickerControllerSourceTypePhotoLibrary because it specifies that the device's photo library is the source for the image picker. This is exactly what we want.
Before moving on, take a look at Figure 13, which contains a sequence diagram that shows the interaction between the WriteReviewViewController and UIImagePickerController objects.
|Figure 13 - The message interaction between the writeReviewController and imagePicker objects|
The objects at the top of the sequence diagram represent the objects that are created from the WriteReviewViewController and UIImagePickerController classes at run time. The dotted lines below the objects are called "lifelines" and indicate the life of an object. The arrows pointing between the objects and between the lifelines are each a single message passed between the objects. Here's the order in which the messages occur:
- The writeReviewViewController object creates an imagePicker object from the UIImagePickerController class;
- The writeReviewViewController object passes a setSourceType: message to the imagePicker object, passing a UIImagePickerControllerSourceTypePhotoLibrary argument. When you set a property value, behind the scenes, a method is actually executed as indicated here;
- The writeReviewViewController object sends a presentViewController: message to itself, passing the imagePicker object as an argument (I've left out the other arguments for the sake of brevity). When the presentViewController: method is executed at run time, the imagePicker object is displayed in the user interface.
I find it's important to visual object interaction so you truly understand how your code works. As you work on your own apps, I recommend creating sequence diagrams for your app's more complicated message sequences.
Testing the Photo Library Integration
Let's take the code we wrote for a spin to see how it works.
- In Xcode, click the Run button;
- When the app appears in the Simulator, click the Write a Review option, and then on the Write Review screen, click on the image view with the Add Image placeholder. Since this is the first time the iAppsReview app has tried to access the photo library, a dialog appears asking if you would like iAppsReview to access your photos. Click on OK to allow it. This displays the Photos screen shown on the left side of Figure 14, which indicates that we have two saved photos. Exactly what we expected. This is the same interface that users are comfortable using in the built-in Photos app;
|Figure 14 - The Photo Library screens of the Image Picker Controller|
- Tap the Saved Photos row and you will be taken to the Saved Photos screen shown in the right side of Figure 14. If you select one of the photos, the image picker controller disappears down through the bottom of the screen. Nothing happens with the image is selected because we haven't written any code to respond to the selection yet.
Retrieving the Selected Image Using a Delegate
If you take another look at the sequence diagram in Figure 13, there doesn't seem to be a way for the WriteReviewViewController object to get the image that is selected in the imagePicker object.
If you read further down in the Apple's documentation for the UIImagePickerController class, you will see the section Providing a Delegate Object as shown in Figure 15.
|Figure 15 - You must provide a delegate object for an image picker controller.|
This documentation states: "To use an image picker controller you must provide a delegate that conforms to the UIImagePickerControllerDelegate protocol." What does this mean?
In this context, delegation refers to one object relying on another object (the delegate) to provide a specified set of functionality. In Objective-C, you can define a specific set of required functionality using a protocol. The fact that the protocol mentioned in the documentation is named UIImagePickerControllerDelegate indicates it's the image picker controller that specifies the functionality it needs to pass back information about the image selected by the user.
If you click on the UIImagePickerControllerDelegate Protocol Reference link in the help topic shown in Figure 9, you are taken to that help topic that defines the methods shown in Figure 16.
|Figure 16 - The UIImagePickerControllerDelegate Protocol methods|
We can ignore the third method in the list, because the documentation tells us it is "deprecated". This is a status that is applied to methods in the Cocoa Touch Framework that should be avoided because they have been superseded by other methods.
Here's a synopsis of the first two methods:
- imagePickerController:didFinishPickingMediaWithInfo: The image picker controller calls this method on the delegate object when the user has finished selecting an image. It passes back information to the delegate describing the media that has been selected;
- imagePickerControllerDidCancel: The image picker controller calls this method on the delegate object when the user clicks Cancel rather than selecting an image.
Ultimately, any object that is the delegate of the image picker controller must have these two methods. Unfortunately, Apple's documentation is somewhat misleading. While it's true that a delegate object must implement the UIImagePickerControllerDelegate, it must also implement the UINavigationControllerDelegate. How do I know this? The UIImagePickerController class has a delegate property that is described in the Apple documentation shown in Figure 17.
|Figure 17 - The delegate property of the UIImagePickerControllerDelegate class|
So bottom line, we need to implement both of these protocols on our delegate object.
Although you can make any object the delegate for the image picker controller, it's typical to make the view controller the delegate object. Let's do that now as outlined in the following steps.
- In the Project Navigator, select the WriteReviewViewController.h header file. We are selecting the header file because this is where you publicly declare (for other classes to see) that the class implements a particular protocol;
- Near the top of the header file, click to the right of the line that begins with @interface;
- We could put the code that implements the protocol on this same line, but it's easier to read if we add it on a second line. To do this, press return and then tab to indent the code a bit;
- Next, type the code shown in Figure 18. This code between angled brackets tells the world (because it's in the public header file) that the WriteReviewViewController class implements the UIImagePickerControllerDelegate and UINavigationControllerDelegate protocols;
|Figure 18 - Implementing the protocols|
- Before we implement the methods that are in the UIImagePickerControllerDelegate protocol, we need to add some variables to the class to hold some of the information that we get back from the image picker controller. Rather than creating local variables within a method, we are going to create variables at the class level. This type of variable is known as an instance variable. This type of variable is very useful, because, unlike local variables, they can be accessed by any method in the class.
To create these instance variables, go to the Project Navigator and select the WriteReviewViewController.m implementation file. Afterwards, scroll to the top of the code file and locate the line of code that begins with the key word @implementation. Click to the right of this line of code, press the space bar, and add the code between curly braces shown in Figure 19.
|Figure 19 - Declaring instance variables|
This code declares an instance variable named image of the type UIImage and another instance variable named imageURL that holds an object of type NSURL;
- So, now we need to implement the methods that are in the UIImagePickerControllerDelegate protocol. To do this, add the methods shown in Figure 20 below the accessPhotoLibrary: method.
Figure 20 - The implementation of the UIImagePickerControllerDelegate methods
The code in the first method sends a dismissViewControllerAnimated:completion: message to itself, which hides the image picker controller after the user selects an image. Afterwards it grabs the image from the info parameter passed back from the image picker controller and stores it in the image instance variable for future use. Next, the code gets the URL of the image (the location of the image on the iOS device) and stores it in the imageURL instance variable, also for future use.
The imagePickerControllerDidCancel: method also dismisses the image picker controller, but doesn't need to do anything afterwards;
- Now there's just one other thing you need to do—set WriteReviewViewController as the delegate for the image picker controller. To do this, add the code shown in Figure 21 to the accessPhotoLibrary: method.
|Figure 21 - Store a reference to the WriteReviewViewController object in the delegate property of the image picker controller.|
To get a clearer picture of how these new messages between the objects work, check out Figure 22.
|Figure 22 - The complete message flow between objects|
In this updated sequence diagram, notice there is a new setDelegate message where the writeReviewViewController object stores a reference to itself in the imagePicker object's delegate property. At the bottom of the diagram, a new message has been added. This message is sent from the imagePicker object to the writeReviewViewController object after the user selects an image. A media argument is sent in this message that contains a collection of objects containing information about the selected image.
Testing the Delegate Code
Now you're ready to test the code and see how it works.
- Click Xcode's Run button;
- When the app appears in the Simulator, click the Write a Review row;
- Click the Photo Album button;
- In the Photos screen, select the Saved Photos row;
- Select either of the two images;
- The image picker controller should disappear and you should see the image displayed as a thumbnail in the image view as shown in Figure 23.
|Figure 23 - The selected photo appears in the image view.|
If you would like a copy of the project as we have completed it so far, just click on this link.
Personally, I find it interesting and fun to add the type of functionality we have added to iAppsReview in this post. Along the way, you have learned a number of new concepts including delegates, protocols, and instance variables. As time goes by and we use these concepts in future posts, they will become far more familiar and an important part of your iOS tool kit.