Using UIImagePickerController in Swift 3

Usually, in iOS applications, users are asked for selecting an image from their device then use it as an avatar of himself/herself throughout the app. In this article, we talk about how to configure and use UIImagePickerController in Swift 3.

Usually, in iOS applications, users are asked for selecting an image from their device then use it as an avatar of himself/herself throughout the app. In this article, we talk about how to configure and use UIImagePickerController in Swift 3.

We’ll set up a UI component displaying a default image. User can click on that default image, replace that default image with another one picked from their device.

In real app, after picking image, we’ll send it to server, but for the simplicity of this tutorial, we don’t go further than picking image — compressing image (who wants to store a huge image on server) — then extract image information for further use.

If you’re already know UIImagePickerController and want to bypass the length explanation, just go ahead and pick up the final code at the end of the article.

1. Set up components

import UIKit

class ViewController: UIViewController {

    let profileImage: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(#imageLiteral(resourceName: "add-photo").withRenderingMode(.alwaysOriginal), for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    let submitButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Submit", for: .normal)
        button.setTitleColor(.black, for: .normal)
        button.layer.borderColor = UIColor.black.cgColor
        button.layer.borderWidth = 1
        button.layer.cornerRadius = 20
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(profileImage)
        profileImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        profileImage.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -100).isActive = true
        profileImage.widthAnchor.constraint(equalToConstant: 100).isActive = true
        profileImage.heightAnchor.constraint(equalToConstant: 100).isActive = true

        view.addSubview(submitButton)
        submitButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        submitButton.topAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 50).isActive = true
        submitButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
        submitButton.heightAnchor.constraint(equalToConstant: 40).isActive = true

    }
}

Code explanation:

— Define profileImage as UIButton, because we'll need to add action to pick image to this component later on

let profileImage: UIButton = {
     let button = UIButton(type: .system)
     button.setImage(#imageLiteral(resourceName: "add-photo").withRenderingMode(.alwaysOriginal), for: .normal)
     button.translatesAutoresizingMaskIntoConstraints = false
     return button
 }()

When setImage for UIButton, we need to specify .withRenderingMode(.alwaysOriginal), otherwise the image displays as a blue silhouette.

Since we will programmatically add layout constraint anchors to UIButton, to conform XCode, we need to set

button.translatesAutoresizingMaskIntoConstraints = false

— Add profileImage to view and define its layout anchors

override func viewDidLoad() {
    super.viewDidLoad()

    // Add profileImage to view and constraint it to center of the screen
    view.addSubview(profileImage)
    profileImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    profileImage.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -100).isActive = true
    profileImage.widthAnchor.constraint(equalToConstant: 100).isActive = true
    profileImage.heightAnchor.constraint(equalToConstant: 100).isActive = true
 }

Our profileImage is constrained by 4 layout anchors:

  • Center it horizontally on screen
  • Center it vertically, but pull it 100px to the top
  • profileImage dimension is 100x100

— Define submitButton as UIButton using the same way we did with profileImage

let submitButton: UIButton = {
    let button = UIButton(type: .system)
    button.setTitle("Submit", for: .normal)
    button.setTitleColor(.black, for: .normal)
    button.layer.borderColor = UIColor.black.cgColor
    button.layer.borderWidth = 2
    button.layer.cornerRadius = 20
    button.translatesAutoresizingMaskIntoConstraints = false
    return button
}()

Because we want the button look “minimalist”, let’s set its colour to black and add a black rounded border:

button.setTitleColor(.black, for: .normal)
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 1
button.layer.cornerRadius = 20

Don’t forget to set translatesAutoresizingMaskIntoConstraints to false so that we can define its layout constraint anchors later on with peace ☮️

Add submitButton to view and define its layout anchors

override func viewDidLoad() {
    super.viewDidLoad()
    ...

    // Add submitButton to view, center it under profileImage
    view.addSubview(submitButton)
    submitButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    submitButton.topAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 50).isActive = true
    submitButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
    submitButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
}

Similar to profileImage, submitButton will be:

  • Center horrizontally on screen
  • Padding 50px from the profileImage. Bottom of profileImage to Top of submitButton = 50px
  • submitButton dimension is 100x40

Now build and run the app, it should look like this:

View with profileImage and submitButton

2. Associate profileImage with action to pick image form device

Using addTarget to associate profileImage with a custom function

let profileImage: UIButton = {
    ...
    button.addTarget(self, action: #selector(handleProfileImage), for: .touchUpInside)
    return button
}()

func handleProfileImage() {
    print("Start picking image")
}

This code tells profileImage to call function handleProfileImage when the finger is inside the bounds of the control - .touchUpInside.

At this point, try running the app, tapping the image, you’ll see “Start picking image” printing out in XCode console log.

🙌👏👍🤘🤞

Instead of printing out a string, we’ll make the app more useful by calling UIImagePickerController

import UIKit
import MobileCoreServices

class ViewController: UIViewController {
    ...
    func handleProfileImage() {
        print("Start picking image")

        let imagePickerController = UIImagePickerController()
        imagePickerController.allowsEditing = true
        imagePickerController.mediaTypes = [kUTTypeImage as String]
        present(imagePickerController, animated: true, completion: nil)
    }
}

Inside handleProfileImage() function, we define imagePickerController of type UIImagePickerController then present that controller modally

Don’t forget to set:

  • imagePickerController.allowsEditing = true to allow user to zoom in/out and crop image before using it
  • imagePickerController.mediaTypes = [kUTTypeImage as String] to filter selectable items showing up on device to type Image only. We don't want user to upload a video for avatar. Remember to import MobileCoreServices to your ViewController in order to use kUTTypeImage
  • In order to access device’s Photo library, which happens to be privacy-sensitive data, we need to declare in Info.plist usage description of accessing Photo.
  • Open Info.plist then add new key Privacy - Photo Library Usage Description, set its value to "We'd like to access your Photos" or what ever you want to tell user when asking for access to their Photos

Add key “Privacy — Photo Library Usage Description” to Info.plist

Now run the app, click profileImage, a message prompt out asking if you allow it to access Photos. Click "OK" to allow, now you are in your photo -- select a photo, edit it, then hit Choose to finish. The imagePickerController dismisses, carrying your selected photo, and nothing happens next ... yet.

message prompt

Now it’s time to decide what to do with the selected image

At this point, we need to decide what to do with the selected image. In this app, we will replace the default image with our selected image.

First, extend current ViewController with UINavigationControllerDelegate and UIImagePickerControllerDelegate, then set imagePickerController.delegate = self

class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    func handleProfileImage() {
        ...
        imagePickerController.mediaTypes = [kUTTypeImage as String]
        imagePickerController.delegate = self
        present(imagePickerController, animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        print("Finish picking image", info)
    }
}

After doing that, it’s possible to call method didFinishPickingMediaWithInfo of imagePickerController, this is where we get details of the selected image. It's a very good timing to put a break point in XCode to see what is it inside info

Now it’s time to update profileImage with selected image

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    print("Finish picking image", info)

    if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
        profileImage.setImage(editedImage.withRenderingMode(.alwaysOriginal), for: .normal)
    } else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
        profileImage.setImage(originalImage.withRenderingMode(.alwaysOriginal), for: .normal)
    }

    profileImage.layer.cornerRadius = profileImage.frame.width / 2
    profileImage.layer.masksToBounds = true
    profileImage.layer.borderWidth = 1
    profileImage.layer.borderColor = UIColor.black.cgColor

    dismiss(animated: true, completion: nil)
}

Code explanation:

  • We first check if the selected photo is edited (scaled, cropped …), if yes, set profileImagewith the editedImage, otherwise use originalImage
  • Turn the selected image to round and put in a black border of 1px
  • Dismiss the imagePickerController, return to initial screen. Now you'll see the default image replaced with your new one.

3. Submit the image

In real app, this is the point where the developer want to send image to server. Depend of the server technology, handling uploading image can be different. In this article, we’ll cover compressing image and make it ready to upload only

Now it’s time to take care of the submitButton guy:

Associate submitButton with a handling action

let submitButton: UIButton = {
    ...
    button.translatesAutoresizingMaskIntoConstraints = false
    button.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
    return button
}()

func handleSubmit() {
    print("Submit button clicked")
}

Similar to profileImage we tells submitButton to call method handleSubmit when it's clicked - .touchUpInside

At this point, inside handleSubmit we'd want to

  • Compress image to make it smaller → saving data 🗜
  • Send the image to the moon 🚀
func handleSubmit() {
    guard let profileImage = profileImage.imageView?.image else { return }
    guard let profileImageData = UIImageJPEGRepresentation(profileImage, 0.3) else { return }

    // Now send profileImageData to Server 🚀🚀🚀
}

Here we use UIImageJPEGRepresentation to get data of profileImage, this function comes with compressionQuality parameter.

The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The > > value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 > represents the least compression (or best quality).” — Apple document

4. Completed code with comments

import UIKit
import MobileCoreServices
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    let profileImage: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(#imageLiteral(resourceName: "add-photo").withRenderingMode(.alwaysOriginal), for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(handleProfileImage), for: .touchUpInside)
        return button
    }()

    func handleProfileImage() {
        let imagePickerController = UIImagePickerController()
        imagePickerController.allowsEditing = true

        // Only allow user to select Image in Photos, no Video allowed
        imagePickerController.mediaTypes = [kUTTypeImage as String]

        imagePickerController.delegate = self

        // Show imagePickerController
        present(imagePickerController, animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        print("Finish picking image", info)

        // Use edited image if user scalled, cropped image before selecting
        if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
            profileImage.setImage(editedImage.withRenderingMode(.alwaysOriginal), for: .normal)
        }
        // or just use original image
        else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
            profileImage.setImage(originalImage.withRenderingMode(.alwaysOriginal), for: .normal)
        }

        // Add style to profileImage: turn in rounded, add border
        profileImage.layer.cornerRadius = profileImage.frame.width / 2
        profileImage.layer.masksToBounds = true
        profileImage.layer.borderWidth = 2
        profileImage.layer.borderColor = UIColor.black.cgColor

        // Dismiss imagePickerController, return back to ViewController
        dismiss(animated: true, completion: nil)
    }

    let submitButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Submit", for: .normal)
        button.setTitleColor(.black, for: .normal)
        button.layer.borderColor = UIColor.black.cgColor
        button.layer.borderWidth = 2
        button.layer.cornerRadius = 20
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
        return button
    }()

    func handleSubmit() {
        print("Submit button clicked")

        guard let profileImage = profileImage.imageView?.image else { return }
        guard let profileImageData = UIImageJPEGRepresentation(profileImage, 0.3) else { return }

        // Now send profileImageData to Server 🚀🚀🚀
    }
override func viewDidLoad() {
        super.viewDidLoad()

        // Add profileImage to view and constraint it to center of the screen
        view.addSubview(profileImage)
        profileImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        profileImage.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -100).isActive = true
        profileImage.widthAnchor.constraint(equalToConstant: 100).isActive = true
        profileImage.heightAnchor.constraint(equalToConstant: 100).isActive = true

        // Add submitButton to view, center it under profileImage
        view.addSubview(submitButton)
        submitButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        submitButton.topAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 50).isActive = true
        submitButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
        submitButton.heightAnchor.constraint(equalToConstant: 40).isActive = true

    }
}