Chapter 12 — Camera
Accessing the Camera is as cool as it sounds!
Displaying Images and UIImageView
- Make a copy of the latest Homepwner project and open it in Xcode.
- Edit the DetailViewController.xib file. Add a UIImageView to the view under the other elements.
- Change the content mode of the image view to Aspect Fit. Now pictures from the camera won’t be distorted when shown in the image view.
- We need an outlet to the image view. Option-click on DetailViewController.m. Control-click and drag to the class extension. Name the property
- These are your current properties.
- Navigation bars should be used mostly for navigation. We’ll add a toolbar to the bottom of the view for the button for the camera.
- Find the Toolbar in the Object picker in IB. The easy way to do that is to start typing into the bottom search field. Drag out a toolbar to the bottom of the view.
- There is already a button on the toolbar and we just need to change it to the camera icon. Select the button. This can be tricky at first because the first time you click on it the toolbar itself will be selected. Click on the button again and it will be selected. You should see this in the Attribute Inspector.
- Select Camera from the Identifier popup menu.
- Your toolbar should look like this now. Cool!
Taking pictures and UIImagePickerController
- Next, we need to add an Action to the DetailViewController class. Open IB and the DetailViewController.m. Control-click on the camera button and drag to the body of the file, (not the class extension). Right at the bottom of the file before the
@endwould be a good spot. Make sure you have selected the button, not the toolbar, or this step won’t work. If the “Insert Action” flag doesn’t appear, you have not selected the button.
- Name the action
takePictureand change the Type to UIBarButtonItem.
- That should give you a wired up action method.
- When the camera button is tapped we need to do several things
- Create an instance of the UIImagePickerController class.
- Determine if the device has a camera.
- If there is a camera then set the type of the image picker for camera use.
- If there’s no camera then set the type to let the user pick from the photo library.
- Assign a delegate for the image picker. We’ll use the DetailViewController class as the delegate.
- Here’s the code.
- That sets up the picker, now we have to display it. This is an example of a modal view controller. It takes over the entire screen and is not part of the navigation controller. We have to present the image picker with this line that we add to the above method.
- Run it on a device! If your device has a camera you’ll get the camera.
- If your device does not have a camera, or you run it in the simulator you’ll get this.
- Take a picture or choose one for the photo library. Where’d the image go? Nowhere! We have no code to save the image, that’s next.
- First, did you notice the wicked looking warning that happened when we set the delegate?
- That’s looks awful, but it’s really simple. The delegate for the UIImagePickerController class needs to be a certain type. It has to be the UIImagePickerControllerDelegate, in fact. Our class is not that type yet. How do we add types to a class? Inheritance or protocols! To get rid of the warning You need to add the delegate protocol to the class declaration in the DetailViewController.h file.
- Notice that we’ll also add the UINavigationControllerDelegate for completeness.
- Now that our class is the official delegate for the image picker let’s look at the methods.
UIImagePickerControllerDelegate Protocol Reference
- Closing the Picker
- – imagePickerController:didFinishPickingMediaWithInfo:
- – imagePickerControllerDidCancel:
- – imagePickerController:didFinishPickingImage:editingInfo: Deprecated in iOS 3.0
- Three methods and one is deprecated. One for when the cancel button is tapped and one for when an image is selected. These methods will be called when the user makes their choice. We need
imagePickerController:didFinishPickingMediaWithInfo:to get the chosen image. Here’s the method that needs to be added to DetailViewController.m.
- Run the app, take a picture.
- Sweet! This is getting really cool.
- Now we’re going to make a class to hold the images. Once our app is running we’re going to want to take lots of pictures of everything in the house. Images are big, they can’t fit in RAM all at once. To make it easier to manage them we’ll make an image store class.
- Create a new class. You should name it your 3-letter prefix plus ImageStore. I’m going to use BNRImageStore to stay in sync with the book. (BNR stands for Big Nerd Ranch, btw.)
- Have the class subclass NSObject. Here’s the BNRImageStore.h file with the public methods we need to declare.
- Now switch to BNRImageStore.m and add a class extension. Declare the following property in the extension.
- This class will need an
initmethod that creates the dictionary. (We’ll be looking at those in a little bit.)
- Now we need our
sharedStoremethod to make this a singleton method.
setImage:forKey:method stores an image in the dictionary for the specified key.
imageForKey:method returns an image we saved previously for the given key.
deleteImageForKey:method does just that, but it first makes sure that the key you’ve given it is not
- Let’s review dictionaries for a bit. Remember the NSDictionary and NSMutableDictionary classes?
- Dictionaries allow us to store data in key/value pairs.
- NSDictionary is an immutable dictionary. You can’t change anything about it once it’s created.
- NSMutableDictionary is a mutable dictionary. You can add and delete keys and their values.
- We’re using dictionaries in two places in this part of the application.
- We’re saving images in our BNRImageStore class in a dictionary.
- In the
imagePickerController:didFinishPickingMediaWithInfo:method from the UIImagePickerControllerDelegate protocol, we get the actual image from a dictionary that is sent to the method.
- The Modern Objective-C way to access values in a dictionary.
- The Modern Objective-C way to add a new key/value pair.
self.dictionary[key] = image;
Creating and Using Keys
- We need to add the key for an image to the BNRItem class. Open the BNRItem.h file and add this property.
- Next, open DetailViewController.m and import the BNRImageStore.h file. The imports for that class should look like this now.
- Modify the
imagePickerController:didFinishPickingMediaWithInfo:method to create a unique ID as the key for an image. This is going to demonstrate a very important part of the way Objective-C and the C
Core Foundation interact. First, create the ID.
- Notice that this is a C function returning a Ref type which is a pointer.
- Next, we create a C string object from the Ref object.
- The next part of this is to convert the C string object to an Objective-C NSString object. This is actually quite easy.
- This is our first example of Toll-free Bridging. Many Core Foundation C ‘objects’, which are not true objects, can be used directly as real Objective-C object.
- The final part is to add the image to the store with the new key.
- Here’s the full method so far. We’re still not done with it yet, though!
Core Foundation and Toll-free Bridging
- One of the reasons Objective-C works so well with the C underneath it is Toll-free Bridging. The way the earlier, and very efficient, C was done was to have C mimic objects. They used a style of coding that was very Object-oriented without having a true OO language. It was done so well that those C ‘objects’ can be exchanged with their Objective-C equivalents. If you remember that Objective-C objects are really C structs this doesn’t seem so strange.
- A CFStringRef can be used as an NSString. The other way around works too. The two objects have a bridge between them that is so easy to travel the term toll-free came to mind. So the name was born. Toll-free Bridging exists between many objects in Objective-C and Core Foundation. We’ll see this more and more over time.
There’s a catch!
- You knew there would be, didn’t you?
- These C objects can’t be managed by ARC. We have to do that ourselves. This will give you a flavor of life before ARC.
- Once we don’t need the C objects we need to release them manually. This is done with a C function. I bet you guessed that.
- In the
imagePickerController:didFinishPickingMediaWithInfo:method we need to add these lines right after we add the the image to the store.
- OK, the final method!
Wrapping Up BNRImageStore
- Next we have to make the pictures of your items appear when we display the detail page.
- We’ll do this in
viewWillAppear:. Whenever the detail view is about to display it will get the current item, grab the image key, then get the corresponding image from the image store and display it. If there’s no key then clear out the image view. Add this code to the bottom of the method in the DetailViewController.m file.
- One last thing, we need to clear out a previous image if you update the image for an item. We’ll do that in the
imagePickerController:didFinishPickingMediaWithInfo:method in the DetailViewController.m file. Add this to the very top of the method.
Dismissing The Keyboard
- We have two circumstances where it would be good to dismiss the keyboard when using the detail view.
- When a user taps the Return button.
- When a user taps anywhere else on the screen.
- These are both accomplished with delegates.
- The first step is to add the text field delegate protocol to the DetailViewController class declaration.
- Next, we add the
textFieldShouldReturn:we’ve used before. Add this to the DetailViewController.m file.
- Try the app, that takes care of number 1 above. Er, except for the number keyboard.
- Now for when we tap anywhere on the screen.
- The way the book has us do this will work just fine. However, there are other ways to do this that we’ll get to in the near future.
- First, we are going to change the main view of the DetailViewController to be a UIControl instead of a UIView. This works because a UIControl is a subclass of UIView. Our main view therefore is still also a UIView. (Sweet how OO works, eh?)
- Open the DetailViewController.xib file for editing. Change the View to a UIControl.
- Now that we have a UIControl we can attach an action to it. Open the DetailViewController.xib file for editing. Option-click on the DetailViewController.m so they are both in view. Control-click and drag to the body of the code. If the “Insert Action” flag doesn’t appear then you didn’t do the previous step correctly.
- Create an Action named
backgroundTapped. Make sure to change the Event to Touch Up Inside.
- In the new action method we simply have to do this.
- Run the app, add some items, tap one and edit some of its data. When you tap on the background the keyboard disappears.