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:
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:
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:
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:
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:
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:
class ViewController
line in the ViewController.swift file.imageView
, and click the Connect button. Xcode creates the following IBOutlet:@IBOutlet var imageView: UIImageView!
takePicture
, and click the Connect button. Xcode creates a takePicture
IBAction method.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:
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:
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) }