Taking Pictures and UIImagePickerController

In the takePicture(_:) method, you will instantiate a UIImagePickerController and present it on the screen. When creating an instance of UIImagePickerController, you must set its sourceType property and assign it a delegate. Because there is set-up work needed for the image picker controller, you need to create and present it programmatically instead of through the storyboard.

Setting the image picker’s sourceType

The sourceType constant tells the image picker where to get images. It has three possible values:

UIImagePickerControllerSourceType.camera

Allows the user to take a new photo.

UIImagePickerControllerSourceType.photoLibrary

Prompts the user to select an album and then a photo from that album.

UIImagePickerControllerSourceType.savedPhotosAlbum

Prompts the user to choose from the most recently taken photos.

Figure 15.9  Examples of the three sourceTypes

Three source types: camera, photo library, and photo album are shown.

The first source type, .camera, will not work on a device that does not have a camera. So before using this type, you have to check for a camera by calling the method isSourceTypeAvailable(_:) on the UIImagePickerController class:

class func isSourceTypeAvailable(_ type: UIImagePickerControllerSourceType) -> Bool

Calling this method returns a Boolean value for whether the device supports the passed-in source type.

In DetailViewController.swift, find the stub for takePicture(_:). Add the following code to create the image picker and set its sourceType.

@IBAction func takePicture(_ sender: UIBarButtonItem) {

    let imagePicker = UIImagePickerController()

    // If the device has a camera, take a picture; otherwise,
    // just pick from photo library
    if UIImagePickerController.isSourceTypeAvailable(.camera) {
        imagePicker.sourceType = .camera
    } else {
        imagePicker.sourceType = .photoLibrary
    }
}

Setting the image picker’s delegate

In addition to a source type, the UIImagePickerController instance needs a delegate. When the user selects an image from the UIImagePickerController’s interface, the delegate is sent the message imagePickerController(_:didFinishPickingMediaWithInfo:). (If the user taps the cancel button, then the delegate receives the message imagePickerControllerDidCancel(_:).)

The image picker’s delegate will be the instance of DetailViewController. At the top of DetailViewController.swift, declare that DetailViewController conforms to the UINavigationControllerDelegate and the UIImagePickerControllerDelegate protocols.

class DetailViewController: UIViewController, UITextFieldDelegate,
        UINavigationControllerDelegate, UIImagePickerControllerDelegate {

Why UINavigationControllerDelegate? UIImagePickerController’s delegate property is actually inherited from its superclass, UINavigationController, and while UIImagePickerController has its own delegate protocol, its inherited delegate property is declared to reference an object that conforms to UINavigationControllerDelegate.

In DetailViewController.swift, set the instance of DetailViewController to be the image picker’s delegate in takePicture(_:).

@IBAction func takePicture(_ sender: UIBarButtonItem) {

    let imagePicker = UIImagePickerController()

    // If the device has a camera, take a picture; otherwise,
    // just pick from photo library
    if UIImagePickerController.isSourceTypeAvailable(.camera) {
        imagePicker.sourceType = .camera
    } else {
        imagePicker.sourceType = .photoLibrary
    }

    imagePicker.delegate = self
}

Presenting the image picker modally

Once the UIImagePickerController has a source type and a delegate, you can display it by presenting the view controller modally.

In DetailViewController.swift, add code to the end of takePicture(_:) to present the UIImagePickerController.

    imagePicker.delegate = self

    // Place image picker on the screen
    present(imagePicker, animated: true, completion: nil)
}

Build and run the application. Select an Item to see its details and then tap the camera button on the UIToolbar and … the application crashes. Take a look at the description of the crash in the console.

Homepwner[3575:64615] [access] This app has crashed because it attempted to
access privacy-sensitive data without a usage description.  The app's Info.plist
must contain an NSPhotoLibraryUsageDescription key with a string value explaining
to the user how the app uses this data.

When attempting to access private information, such as a user’s photos, iOS presents a prompt to the user asking them whether they want to allow access to the application. Contained within this prompt is a description for why the application wants to access this information. Homepwner is missing this description, and therefore the application is crashing.

Permissions

There are a number of capabilities on iOS that require user approval before use. Here are a subset of those capabilities:

  • Camera and photos

  • Location

  • Microphone

  • HealthKit data

  • Calendar

  • Reminders

For each of these, your application must supply a usage description that specifies the reason that your application wants to access this information. This description will be presented to the user whenever the application accesses that capability.

In the project navigator, select the project at the top. Make sure the Homepwner target is selected and open the Info tab along the top (Figure 15.10).

Figure 15.10  Opening the project info

Screenshot shows the custom iOS target properties.

Hover over the last entry in this list of Custom iOS Target Properties and click the + button. Set the Key of this new entry to be NSCameraUsageDescription and the Type to be a String.

Double-click on the Value for this new row and enter the string This app uses the camera to associate photos with items. This is the string that will be presented to the user.

Now repeat the same steps above to add a usage description for the photo library. The Key will be NSPhotoLibraryUsageDescription of type String and the Value will be This app uses the Photos library to associate photos with items.

The Custom iOS Target Properties section will now look like Figure 15.11. (The entries in your list may be in a different order.)

Figure 15.11  Adding in the new keys

Screenshot shows all the custom iOS Target Properties of the Homepwner target. Several keys are listed along with their respective types and values.

Build and run the application and navigate to an item. Tap the camera button and you will see the permission dialog presented with the usage description that you provided (Figure 15.12 shows the description for the library). After accepting, the UIImagePickerController’s interface will appear on the screen (Figure 15.13 shows the camera interface), and you can take a picture or choose an existing image if your device does not have a camera.

Figure 15.12  Photos library usage description

The photos library usage permission is displayed in a dialog.

(If you are working on the simulator, there are some default images already in the photo library. If you would like to add your own, you can drag an image from your computer onto the simulator, and it will be added to the simulator’s photo library. Alternatively, you can open Safari in the simulator and navigate to a page with an image. Click and hold the image and choose Save Image to save it in the simulator’s photo library.)

Figure 15.13  UIImagePickerController’s preview interface

Screenshot shows a camera interface that features a standstill shot of an iPhone box at default 1x zoom.

Saving the image

Selecting an image dismisses the UIImagePickerController and returns you to the detail view. However, you do not have a reference to the photo once the image picker is dismissed. To fix this, you are going to implement the delegate method imagePickerController(_:didFinishPickingMediaWithInfo:). This method is called on the image picker’s delegate when a photo has been selected.

In DetailViewController.swift, implement this method to put the image into the UIImageView and then call the method to dismiss the image picker.

func imagePickerController(_ picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [String: Any]) {

    // Get picked image from info dictionary
    let image = info[UIImagePickerControllerOriginalImage] as! UIImage

    // Put that image on the screen in the image view
    imageView.image = image

    // Take image picker off the screen -
    // you must call this dismiss method
    dismiss(animated: true, completion: nil)
}

Build and run the application again. Take (or select) a photo. The image picker is dismissed, and you are returned to the DetailViewController’s view, where you will see the selected photo.

Homepwner’s users could have hundreds of items to catalog, and each one could have a large image associated with it. Keeping hundreds of instances of Item in memory is not a big deal. Keeping hundreds of images in memory would be bad: First, you will get a low memory warning. Then, if your app’s memory footprint continues to grow, the OS will terminate it. The solution, which you are going to implement in the next section, is to store images to disk and only fetch them into RAM when they are needed. This fetching will be done by a new class, ImageStore. When the application receives a low-memory notification, the ImageStore’s cache will be flushed to free the memory that the fetched images were occupying.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset