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 ofsubmitButton
= 50px submitButton
dimension is 100x40
Now build and run the app, it should look like this:
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 itimagePickerController.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 importMobileCoreServices
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
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.
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
}
}