In the choosePhotoSource(_:) 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.

Close the editor showing Main.storyboard. In DetailViewController.swift, add a new method that creates and configures a UIImagePickerController instance. You will need to create the UIImagePickerController instance from more than one place, so abstracting it into a method will help avoid repetition.

The sourceType is a UIImagePickerController.SourceType enumeration value and tells the image picker where to get images. It has three possible values:

.camera

Allows the user to take a new photo.

.photoLibrary

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

.savedPhotosAlbum

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

These three options are illustrated in Figure 15.5.

In choosePhotoSource(_:), create an image picker controller instance when the user chooses one of the action sheet options.

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: UIImagePickerController.SourceType) -> Bool

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

Update choosePhotoSource(_:) to only show the camera option if the device has a camera.

With that code added, your cameraAction code may not be indented correctly. An easy way to correct indentation is to highlight the code that you want to correct, open the Editor menu, and select StructureRe-Indent. Since this is a tool you will likely want to use often, the keyboard shortcut Control-I is a handy one to remember.

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.

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.

Next, set the instance of DetailViewController to be the image picker’s delegate in imagePicker(for:).

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 choosePhotoSource(_:) to present the UIImagePickerController.

Apple’s documentation for UIImagePickerController mentions that the camera should be presented full screen, and the photo library and saved photos album must be presented in a popover. The only change you need to make to satisfy these requirements is to present the photo library in a popover.

Update the image picker to do just that.

Build and run the application. Select an Item to see its details and then tap the camera button on the UIToolbar. Choose Photo Library and then select a photo.

If you are working on the simulator, you will notice that the Camera option no longer appears, because the simulator has no camera. However, there are some default images already in the photo library that you can use.

If you have an actual iOS device to run on, you will notice a problem if you try to use the camera. When you select an Item, tap the camera button, and chose Camera, the application crashes.

Take a look at the description of the crash in the console:

    LootLogger[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 NSCameraUsageDescription key with a string value explaining
    to the user how the app uses this data.

When attempting to access potentially private information, such as the camera, iOS prompts the user to consent to that access. Contained within this prompt is a description of why the application wants to access the information. LootLogger is missing this description, and therefore the application is crashing.

There are a number of capabilities on iOS that require user approval before use. The camera is one. Some of the others are:

  • 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 the capability or information. This description will be presented to the user when the application attempts the access.

In some cases, iOS manages user privacy without the alert. When selecting a photo from the photo library using UIImagePickerController, users confirm the photo they want to use with the Choose button. No usage description is required. On the other hand, the photo library usage description is required when the application wants to use the Photos framework to access the library silently.

In the project navigator, select the project at the top. In the editor, make sure the LootLogger target is selected and open the Info tab along the top (Figure 15.6).

Hover over the last entry in this list of Custom iOS Target Properties and click the Opening the project info button. Set the Key of the new entry that appears to NSCameraUsageDescription and the Type to String. You will not find this key in the drop-down menu; you must type in its name. And the key is case sensitive, so make sure to type it in correctly.

When you press Return, the key name in Xcode will change from NSCameraUsageDescription to Privacy – Camera Usage Description. By default, Xcode displays human-readable strings instead of the actual key names. When adding or editing an entry, you can use either the human-readable string or the actual key name. If you would like to view the actual key names in Xcode, Control-click on the key-value table and select Raw Keys & Values.

For the Value, enter This app uses the camera to associate photos with items. This is the string that will be presented to the user. The Custom iOS Target Properties section will look similar to Figure 15.7.

Build and run the application on a device and navigate to an item. Tap the camera button, select the Camera option, and you will see the permission dialog presented with the usage description that you provided (Figure 15.8). After you tap OK, the UIImagePickerController’s camera interface will appear on the screen, and you can take a picture.

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 imagePickerController(_:didFinishPickingMediaWithInfo:) to put the image into the UIImageView and then call the method to dismiss the image picker.

The image that the user selects comes packaged within the info dictionary. This dictionary contains data relevant to the user’s selection, and its contents will vary depending on how the image picker is configured. For example, if the image picker is configured to allow image editing, the dictionary might also contain the .editedImage and .cropRect keys. There are other ways to configure an image picker and other keys that can be returned in the info dictionary, so take a look at the UIImagePickerController documentation if you are interested in learning more.

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

LootLogger’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. But 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.