Using images from Gallery or Camera in iOS app

The iOS SDK provides access to both the camera device and photo library through the UIImagePickerController class. This allows videos and photographs to be taken from within an application and for existing photos and videos to be presented to the user for selection.

In order to use the UIImagePickerController, an instance of the class must first be created. In addition, properties of the instance need to be configured to control the source for the images or videos (camera, camera roll or library). Further, the types of media that are acceptable to the application must also be defined (photos, videos or both). Another configuration option defines whether the user has the option to edit a photo once it has been taken and before it is passed to the application.

Selecting photos from photo library with the UIImagePickerController

Let’s learn how to use UIImagePickerController to let the user select a photo from their device to load into your app.

The UIImagePickerController can handle selecting images from a user’s photo library. Although the interface is vastly different from the interface for taking photos, the code you need to implement is quite similar, with three main differences:

  • Access to the photo library requires a different permission, and so a different explanation. You need to set the explanation in your app’s Info.plist file under Privacy - Photo Library Usage Description (NSPhotoLibraryUsageDescription). Something like this: "This app wants to use your photos".
  • You need to adjust the sourceType of the image picker controller to .photoLibrary.
pickerController.sourceType = .photoLibrary

Alternatively, you can use savedPhotosAlbum, which only shows the device’s camera roll.

let pickerController = UIImagePickerController()
pickerController.delegate = self
pickerController.allowsEditing = true
pickerController.mediaTypes = ["public.image", "public.movie"]
pickerController.sourceType = .photoLibrary

Allows editing is a flag that indicates if the resizing & cropping interface should be presented after selecting & taking a picture, if true you should use the .editedImage instead of the .originalImage key - inside the picker delegate - to get the proper image from the image info dictionary.

There are 3 available source types: .camera, which is the camera, and there are two other options to get pictures from the photo library. The .photoLibrary enum case will give you full access, but you can limit the selection scope only for the camera roll if you choose .savedPhotosAlbum.

The delegate should implement both the UIImagePickerControllerDelegate and the UINavigationControllerDelegate protocols.

This is going to be a very simple storyboard. It’s going to be a button, and a UIImageView.

class ViewController: UIViewController, UIImagePickerControllerDelegate, 
    UINavigationControllerDelegate {

    @IBOutlet var imageView: UIImageView!

    let pickerController = UIImagePickerController()

    @IBAction func loadImageButtonTapped(_ sender: UIButton) {
        pickerController.allowsEditing = false
        pickerController.sourceType = .photoLibrary

        present(pickerController, animated: true, completion: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        pickerController.delegate = self
    }

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

        //var newImage: UIImage
        //if let possibleImage = info[.editedImage] as? UIImage {
        //    newImage = possibleImage
        //} else if let possibleImage = info[.originalImage] as? UIImage {
        //    newImage = possibleImage
        //} else {
        //    return
        //}

        if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            imageView.contentMode = .scaleAspectFit
            imageView.image = pickedImage
        }

        dismiss(animated: true, completion: nil)
    }

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }
}

Apple recommends in the documentation for UIImagePickerController that you present your image picker as a popover when picking from a photo library or saved photos album.

You need to present your image picker as a popover simply by updating its modal presentation style, and specifying the popover’s anchor point:

pickerController.modalPresentationStyle = .popover 
pickerController.popoverPresentationController?.barButtonItem = galleryButton

YPImagePicker

Also we can use an external library to handle the complex process of rendering all the user's media, picking an image, and applying a filter for us. The name of the library is YPImagePicker.

This is an open source library that implements an Instagram-like photo and video picker in Swift. Add the library to the Podfile, by inserting the pod 'YPImagePicker' line to the file.

The presentPicker function is called once we detect that the button is clicked. The following code shows the custom image picker view controller:

func presentPicker() {
    var config = YPImagePickerConfiguration()
    config.onlySquareImagesFromLibrary = false
    config.onlySquareImagesFromCamera = true
    config.libraryTargetImageSize = .original
    config.usesFrontCamera = true
    config.showsFilters = true
    config.shouldSaveNewPicturesToAlbum = false
    config.albumName = "My Album"
    config.startOnScreen = .library

    let picker = YPImagePicker(configuration: config)
    picker.didSelectImage = { img in 
        // ...
    }
    present(picker, animated: true, completion: nil)
}

If we try to run the app, it will crash. iOS has to ask the user for appropriate permissions to access the user's photos. To do so, it needs special keys to be defined in the Info.plist file and associated with short description texts. The problem is that we should let the user know why the app needs access to the Photos library, device camera, and microphone. Thus, we have to add a description of why the app needs those. A system alert will be displayed and the user should agree before moving forward. If the user declines, then we should handle this case and let them know that this is a critical part of our app, without which it can't function properly.

To add these descriptions, you have to open the Info.plist file. Then, start adding the following keys and values:

<key>NSCameraUsageDescription</key>
<string>MyApp needs access to your camera</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>MyApp needs access to your photos</string>

<key>NSMicrophoneUsageDescription</key>
<string>MyApp needs access to your microphone</string>

When the descriptions are added, the application should function, but once we pick a nice image and apply a filter, we can't leave the picker. The problem lies in the picker.didSelectImage closure. We have to dismiss the screen manually when an image is picked. If you start the app and press the camera button, you will see screens that show your photo gallery images in a grid layout.

The powerful picker component doesn't implement the last step, which is adding a caption and the actual post sharing when you press Done. We have to create a new screen to do that. Let's start with the UI first. Open the storyboard and create a new view controller.

Then, add a UIImageView with a width and height equal to 110 points. Next to it, place a TextView , which will allow the user to add a short description to the images.

You need a new Swift file, CreatePostVC.swift, and a new class, CreatePostVC , which should be associated with the new view controller. Don't forget to set the storyboard ID of that controller, similar to what we did at the beginning of this chapter for a couple of other controllers:

class CreatePostVC: UIViewController {
    override var prefersStatusBarHidden: Bool { return true }
    private let placeHolderText = "Write ..."
    public var image: UIImage?
    @IBOutlet weak var photo: UIImageView!
    @IBOutlet weak var textView: UITextView! {
        didSet {
            textView.textColor = .gray
            textView.text = placeHolderText
            textView.selectedRange = NSRange()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
        photo.image = image
        navigationItem.rightBarButtonItem = UIBarButtonItem (
        title: "Share", style: .done, target: self, action: #selector(createPost))
    }

    @objc func createPost() {
        self.dismiss(animated: true, comletion: nil)
    }
}

The view controller adds a Share button navigation item at the top right. The button should create the new post and close the picker, bringing the user back to their previous activity.

Now, let's try to hook our new CreatePostVC to the picker. This will be done in PhotoController. We have to handle that in the didSelectImage closure:

picker.didSelectImage = { [unowned picker, weeak self] img in 
    if let vc = self?.storyboard?.instantiateViewController(withIdentifier: "CreatePost") as? CreatePostVC {
        vc.image = img 
        let transition = CATransition()
        transition.duration = 0.3
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEasyInEasyOut)
        transition.type = kCATransitionFade
        picker.view.layer.add(transition, forKey: nil)
        picker.pushViewController(vc, animated: false)
    }
}

In the closer, the picker is unowned because it will be initialized and we don't want to create a memory leak (reference cycle). The self should be weak, because we use it and we don't want to create another memory leak.

Using the Camera

To access the camera in an iOS device, you need to follow several steps:

  1. Set privacy settings in the Info.plist file to request access to both the camera and the photo library.
  2. Use the Image picker controller to access the camera (and check to make sure the iOS device has a camera).
  3. Display the image on the screen so the user can capture an image.
  4. Optionally save the image in the photo library.
You can only test the camera on a real iOS device such as an iPhone or iPad because the Simulator cannot duplicate a camera.

By default, no app can access the camera in an iOS device for privacy reasons. This prevents apps from recording images without the user’s knowledge. So if an app wants to access the camera, it must request permission. There are two privacy settings you need to modify in the Info.plist file:

  • Privacy – Camera Usage Description
  • Privacy – Photo Library Additions Usage Description

The Privacy – Camera Usage Description key in the Info.plist file requests permission to access the camera. The Privacy – Photo Library Additions Usage Description key requests permission to store images in the Photos library. Only if the user grants permission to accessing the camera and the photo library can an app retrieve images through the camera and save them in the Photos library.

To see how to set privacy settings in an app, follow these steps:

  1. Click the Info.plist file in the Navigator pane.
  2. Move the mouse pointer over the last row displayed. A + and – button appears.
  3. Click the + button to add a new row in the Info.plist file. Xcode displays a popup menu.
  4. Scroll down the list and choose Privacy – Camera Usage Description.
  5. Click in the Value column and type any arbitrary text to display to the user such as Need to access camera.
  6. Move the mouse pointer over the last row until a + and – button appears.
  7. Click the + button to add a new row. Xcode displays a popup menu.
  8. Scroll down the list and choose Privacy – Photo Library Additions Usage Description.
  9. Click in the Value column and type any arbitrary text to display to the user such as Need to access photo library. You should now have two privacy keys in the Info.plist.

Once you’ve defined the two privacy settings to access the camera and photo library, you’ll be ready to design the user interface and write Swift code.

Checking for a Camera. Most iOS devices come with a built-in camera. However, older iOS devices, such as the first iPod touch and early iPad models, did not come with a camera. In case your app may run on older iOS devices without a camera, you need to check to make sure a camera is available.

To access the camera in an iOS device, we need to use the UIImagePickerController. This allows us to not only detect if a camera exists but also to specify which camera to use, the front or rear camera. If you don’t specify a camera to use, your app will default to using the rear camera.

To check if a camera exists, follow these steps:

Edit the class ViewController line as follows:

class ViewController: UIViewController, UIImagePickerControllerDelegate, 
    UINavigationControllerDelegate {}

This allows the ViewController.swift file to access the camera through the image picker controller and view the image that the camera currently sees.

Edit the viewDidLoad method as follows:

override func viewDidLoad() {
    super.viewDidLoad()
    if !UIImagePickerController.isSourceTypeAvailable(.camera) {
        let alertController = UIAlertController.init(title: nil, 
            message: "No camera available.", 
            preferredStyle: .alert)
        let okAction = UIAlertAction.init(title: "OK", 
            style: .default, 
            handler: {(alert: UIAlertAction!) in })
        alertController.addAction(okAction)
        self.present(alertController, animated: true, completion: nil) 
    }
}

This code simply checks if a camera is available. If it is not true that a camera is available, then it displays "No camera available" in an alert that pops up on the screen. In a shipping app, you’d also want the app to shut down if it lacks a camera.

The user interface for our app will consist of the following:

  • Two buttons
  • A single image view

One button will access the camera and let us take a picture. After we take a picture, we can show that picture in the image view. Now we’ll be able to use the second button to save the picture into the Photos library.

To create the user interface for our app, follow these steps:

  1. Click the Main.storyboard file in the Navigator pane.
  2. Click the Library icon and drag and drop two buttons and an image view onto the user interface.
  3. Double-click one button, type Take Picture, and press Enter.
  4. Double-click the second button, type Save Picture, and press Enter.
  5. Choose Editor > Resolve Auto Layout Issues > Reset to Suggested Constraints. Xcode adds constraints to the buttons and image view.
  6. Click the Assistant Editor icon in the upper right corner of the Xcode window. Xcode shows the Main.storyboard side by side with the ViewController.swift file.
  7. Move the mouse pointer over the image view, hold down the Control key, and Ctrl-drag under the class ViewController line in the ViewController.swift file.
  8. Release the Control key and the left mouse button. A popup window appears.
  9. Click in the Name text field, type imageView, and click the Connect button. Xcode creates the following IBOutlet:
@IBOutlet var imageView: UIImageView!
  1. Move the mouse pointer over the Take Picture button, hold down the Control key, and Ctrl-drag above the last curly bracket at the bottom of the ViewController.swift file.
  2. Release the Control key and the left mouse button. A popup window appears.
  3. Click in the Name text field, type takePicture, and click the Connect button. Xcode creates a takePicture IBAction method.
  4. Move the mouse pointer over the Save Picture button, hold down the Control key, and Ctrl-drag above the last curly bracket at the bottom of the ViewController.swift file.
  5. Release the Control key and the left mouse button. A popup window appears.
  6. Click in the Name text field, type savePicture, and click the Connect button. Xcode creates a savePicture IBAction method.

Taking a Picture. Before taking a picture, we first verify that the device has a camera. Then we use UIImagePickerController and define its source to be the camera in the iOS device. By default, the UIImagePickerController will use the rear camera, but if we want to specify the front camera, we’ll need to use the following:

let picker = UIImagePickerController()
picker.cameraDevice = UIImagePickerController.CameraDevice.front

After we take a picture and capture an image, we need to store that image in the image view and dismiss the camera view. To see how to take a picture and display it in the image view, follow these steps:

  1. Click the ViewController.swift file in the Navigator pane.
  2. Edit the takePicture IBAction method as follows:
@IBAction func takePicture(_ sender: UIButton) {
    if (UIImagePickerController.
        isSourceTypeAvailable(UIImagePickerController.SourceType.camera)){

        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = UIImagePickerController.SourceType.camera
        //picker.cameraDevice = UIImagePickerController.CameraDevice.front
        self.present(picker, animated: true, completion: nil) 
    }
}

The if statement checks to make sure a camera exists in the iOS device. If so, then it creates a UIImagePickerController object, sets the delegate to the ViewController.swift file, and accesses the camera through the image picker controller. By default, the camera chosen will be the rear camera, but we can specify the front camera. Finally, the image displayed in the camera appears on the screen.

Now we need to write two additional functions. First, the camera view will display a Cancel button so we need to make this Cancel button hide the camera view. Second, if the user takes a picture, we need to hide the camera view and display this image in the image view.

Add the following two functions in the ViewController.swift file:

func imagePickerController(_ picker: UIImagePickerController, 
    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let capturedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
        picker.dismiss(animated: true, completion: nil) 
        imageView.contentMode = .scaleToFill 
        imageView.image = capturedImage
    } 
}

func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    picker.dismiss(animated: true, completion: nil) 
}

Saving a Picture. After the user takes a picture with the camera, our app displays that image in the image view. Now the user has the option of saving this image in the Photos library.

To save images in an image view and store them in the Photos library, follow these steps:

  1. Click the ViewController.swift file in the Navigator pane.
  2. Modify the savePicture IBAction method as follows:
@IBAction func savePicture(_ sender: UIButton) { 
    let imageData = imageView.image!.pngData()
    let compressedImage = UIImage(data: imageData!)

    UIImageWriteToSavedPhotosAlbum(compressedImage!, nil, nil, nil)
    let alert = UIAlertController(
        title: "Saved", 
        message: "Your image has been saved", 
        preferredStyle: .alert)
    let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
    alert.addAction(okAction)
    self.present(alert, animated: true, completion: nil) 
}

Choosing between Camera and Photo library

First, you check whether the camera is available. When it is, you show an action sheet to let the user choose between the camera and the Photo Library.

func pickPhoto() {
    if UIImagePickerController.isSourceTypeAvailable(.camera) {
        showPhotoMenu()
    } else {
        choosePhotoFromLibrary()
    }
}

func showPhotoMenu() {
    let alert = UIAlertController(title: nil, message: nil,
    preferredStyle: .actionSheet)

    let actCancel = UIAlertAction(title: "Cancel", style: .cancel,
    handler: nil)
    alert.addAction(actCancel) 

    let actPhoto = UIAlertAction(title: "Take Photo",
    style: .default, handler: { _ in
        self.takePhotoWithCamera() 
    })

    alert.addAction(actPhoto)

    let actLibrary = UIAlertAction(title: "Choose From Library",
    style: .default, handler: { _ in
        self.choosePhotoFromLibrary() 
        })

    alert.addAction(actLibrary) 

    present(alert, animated: true, completion: nil)
}