Almost every iPhone and iPad now has a camera. Many people want to use a camera in their apps for various reasons. While you may not be building the next Instagram or Twitter, there are many places where photo documentation comes in handy.
There are two ways to use the camera. The more powerful and advanced method is using AVFoundation
, but it is also a very complicated way to get a photo out of the camera.
The simpler way is the UIImagePicker
. This is a quick way as Swift has simplified much of the code for it. It is also the best way to fetch photos from the photo album. In this lesson, we’ll set up a basic camera and library app. In upcoming posts we’ll add a few embellishments to it that will make for a more effective camera.
Set Up The Layout
We’ll be talking about some of the issues with camera on both iPad and iPhone, so we’ll make this a universal app. We will be using a dab of auto layout as well. If you want a brief introduction to auto layout go over and take a look at my book Practial Autolayout. You won’t need it for this lesson, but it may make what I do a little more understandable.
Make a new project SwiftPizzaCam with a single view and using Swift. As already mentioned, make the device Universal.
If you are not familiar with Xcode 8’s interface builder bar and the auto layout icons, here is a guide to help you as we go through setting up the app.
Click on the iPhone 6s class preview. A new selection of previews appears.
Select the iPad 9.7″ device. then zoom out to 50%
Xcode 8 come with a series of easily accessible preview modes in interface builder. We will put a photo into an UIImageView
when we first use the image, and doing so on an iPad is easier than an iPhone, though it will work on both. Right-click and save the image below:
Click on Assets.Xcassets and drag the pizza file into the assets folder. Go back to the story board and select the clip icon. You will find the pizza media there.
Drag out the pizza to the view and drop it into the view controller.
Click the pin button. Click off Constrain to Margins. Click on all the I-beams, then set their value to 0 points like the image below:
Be sure to press tab after you type in any number in this popover. It has an annoying habit of forgetting them if you don’t. Select Update Frames: Items of New Constraints towards the bottom. Click Add 4 Constraints. In the properties change the View Mode to AspectFit to properly size the photo.
Now drag out a label to the storyboard. Set the background property to a Light Gray( #AAAAAA) color with a 65% alpha. Center Align the label text with a 28 point font size. Change the text of the label to read Pizza Cam!!!. Select the label, and then click the pin button . Set the top 8 points, left and right sides 0 points, but not the bottom, like this:
Make sure you select Update Frames: Items of New Constraints towards the bottom before clicking Add 3 Constraints.
Drag out a toolbar and place toward the bottom of the view. Using the pin menu , Pin it to the bottom, left and right like this, again updating the frames:
Add a bar button item and a flexible space bar item to the toolbar. In one of the two bar buttons label it Photo and the other Library. Your tool bar and layout should look like this:
Select the iPhone 6s in the preview modes. It should look like this:
If you did everything correctly, you should have no auto layout warnings. If you do, go to the resolver and click Update Frames on the bottom half of the menu that appears. If things get messier after doing this, clear the constraints on the bottom of the resolver, and pin everything again.
Wire Up the Outlets
Your next step is to wire up all the outlets and actions. Open the assistant editor. Control-drag from the pizza photo image and make an outlet called myImageView. Control drag from the Library button and make an action called photoFromLibrary with a sender of type UIBarButtonItem
. This is important for stuff we will do later. Do this again, but with the Photo button and named shootPhoto. Again, make the sender UIBarButtonItem
.
You can clean up the code a bit if you like. When done you should have something like this:
import UIKit class ViewController: UIViewController{ @IBOutlet weak var myImageView: UIImageView! @IBAction func shootPhoto(_ sender: UIBarButtonItem){ } @IBAction func photofromLibrary(_ sender: UIBarButtonItem) { } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } }
Add the UIImagePickerController and Delegate
The star of our show the UIImagePickerController
is missing. We do that programmatically. Close the assistant editor, and open ViewController.swift. Add the following line under the outlet for myImageView
:
let picker = UIImagePickerController()
The UIImagePicker
does much of its works from a delegate. Add the following to the class description:
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
We actually have two delegates: UIImagePickerControllerDelegate
and UINavigationControllerDelegate
. The UINavigationControllerDelegate
is required but we do nothing with it. In the viewDidLoad
, add the following:
override func viewDidLoad() { super.viewDidLoad() picker.delegate = self }
We have wired up the delegate. Unlike other delegates, there is no required methods. However, this will not work without implementing two methods. At the bottom of the class, add the following:
//MARK: - Delegates func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { }
These two methods handle our selections in the library and camera. We can either handle the cancel case with imagePickerControllerDidCancel
or handle media with didFinishPickingMediaWithInfo
Getting a Photo from the Library
The UIImagePickerController
is a view controller that gets presented modally. When we select or cancel the picker, it runs the delegate, where we handle the case and dismiss the modal. Let’s implement the photo library first, then the delegates. Add the following code to the photoFromLibrary
method:
@IBAction func photoFromLibrary(_ sender: UIBarButtonItem) { picker.allowsEditing = false picker.sourceType = .photoLibrary picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)! present(picker, animated: true, completion: nil) }
To get to the photo picker, it is three lines of code. We already initialized picker
. In line two, we tell the picker we want a whole picture, not an edited version. In line three, we set the source type to the photo library. Line four we set the media types for all types in the photo library. Line five calls present
to present the picker in a default full screen modal.
If you build and run, you could press the photoFromLibrary
method, but then get stuck in the library. We need delegates to get out of the library. First let’s add the code for the imagePickerDidCancel
delegate method:
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) }
This does nothing special: it dismisses the modal controller. If we do pick a photo, we want to do something with the photo. Add the following code to the didFinishPickingMediaWithInfo
delegate method:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2 myImageView.contentMode = .scaleAspectFit //3 myImageView.image = chosenImage //4 dismiss(animated:true, completion: nil) //5 }
One of the parameters of this method is info
. It has a dictionary of various information about the selected media, including metadata, a user edited image if the .allowsEditing
property is true
, and a NSURL
location of the image. For our camera app, the important key is the UIImagePickerControllerOriginalImage
. We take that key and put the image into a variable. We could make this a constant, but there is some future iterations of this project that will need this as a variable.
Camera images are usually bigger than a UIImageView
on a device. To shrink them quickly to a visible size, the best way is to set the .contentView
property of our UIImageView
to .ScaleAspectFit
as we do in line 3.
Security for the Photo Library
Run in the simulator as an iPhone 6s. You should get a screen like this:
Tap the Library bar button. The app crashes:
In the simulator, there’s no message about what happened, the system just ends the application. Apple requires any use of the camera, the photo library or any personal information for that matter asks the user to agree sharing information with the app on the first use. For the UIImagePicker
, this is included in your first run of the app. Starting with iOS10, there’s another layer of security on the photo library. The developer must make an entry in the info.plist describing why they want to use the photo. That description will show up in an alert when the user decides to allow access to the library.
There’s two ways to add this entry: in XML or in the property list. We’ll use the property list first. In Xcode, select Info.plist from the Navigator pane
Right click on the Information Property List Row. In the menu that appears, select Add Row.
You get a new row with a drop down menu. Select Privacy – Photo Library Usage… from the menu.
With the row still highlighted, click the value column and add why you want access to the photo library. In this app we are setting a background image.
If you wish to add this directly to XML, the key/value is this
NSPhotoLibraryUsageDescription Set the Background
We’ll come back to this for the camera, and do it in XML.
Build and Run again. Click the Library button, and you will get the permissions alert with the description we placed in the property list:
Tap OK. We get our Image picker,
When we tap a photo, it appears on the app.
Running as an iPhone and iPad app: Using Popovers
Stop the app and run as an iPad 2. After answering OK to the library privacy question you get this:
Which looks fine, except Apple doesn’t like it. They want you to use a popover. Popovers must access the photo library on iPads. Fortunately, it is very easy to get the popover to work. Add the highlighted lines in photoFromLibrary
@IBAction func photoFromLibrary(_ sender: UIBarButtonItem) { picker.allowsEditing = false picker.sourceType = .photoLibrary picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)! picker.modalPresentationStyle = .popover present(picker, animated: true, completion: nil) picker.popoverPresentationController?.barButtonItem = sender }
Line 5 selects a presentation style of a popover. We then present the popover. Line 7 sets the reference point for the popover to the bar button item. As I mentioned in another post popovers need a reference rectangle to pop up from. In this case, we can use the UIBarButtonItem
which happens to be sender . This is why it was so important to use UIBarButtonItem
as the sender type.
Build and run with the iPad 2 simulator. Now we have a popover to pick a photo.
Since present
checks the class size, you will get the proper behavior on any phone .The iPhone 6s Plus in the simulator does this.
We save ourselves from hours of device fragmentation work this way.
Adding a Camera
Basic Camera code is almost the same as adding a photo library. Change the shootPhoto
code to this:
@IBAction func shootPhoto(_ sender: UIBarButtonItem) { picker.allowsEditing = false picker.sourceType = UIImagePickerControllerSourceType.camera picker.cameraCaptureMode = .photo picker.modalPresentationStyle = .fullScreen present(picker,animated: true,completion: nil) }
We changed the sourceType
property to .camera
and specified the cameraCaptureMode
property to .photo.
The camera, unlike the photo library, is required to be full screen, so we don’t make it a popover, but explicitly a .FullScreen
.
Like the library, we have another info.plist entry to make. This time we’ll use XML. Right click the info.plist and in the menus select Open As> Source Code:
Just below the tag add this:
NSCameraUsageDescription Set the background of the app with your beautiful photography
We’re ready to run. However, if you build and run this in the simulator, you will crash. In order to test and use camera code you have to use a real connected device. I connected my iPad Pro and ran the code. When I pressed the photo button, I again get the message about allowing access.
Tapping OK, I get the camera:
Take a picture, and the app asks me if I want to keep it.
The app returns with my photo.
Preventing the Camera Crash
This all works, but we really want to prevent that crash if there is no camera. Add the highlighted lines to shootPhoto
:
@IBAction func shootPhoto(_ sender: UIBarButtonItem) { if UIImagePickerController.isSourceTypeAvailable(.camera) { picker.allowsEditing = false picker.sourceType = UIImagePickerControllerSourceType.camera picker.cameraCaptureMode = .photo picker.modalPresentationStyle = .fullScreen present(picker,animated: true,completion: nil) } else { noCamera() } }
Line 2 uses the class method isSourceTypeAvailable
to checks if we have a camera. If there is, run the camera. If not explain to the user the device does has noCamera
. For that function, we’ll add an alert like this, using UIAlertController
:
func noCamera(){ let alertVC = UIAlertController( title: "No Camera", message: "Sorry, this device has no camera", preferredStyle: .alert) let okAction = UIAlertAction( title: "OK", style:.default, handler: nil) alertVC.addAction(okAction) present( alertVC, animated: true, completion: nil) }
Line 2 makes an alert with the proper message. We add an OK action button on lines 6 through 10, then present the alert as a modal in line 11 onwards. With this code, if you run in the simulator, you get this when you attempt to take a picture:
The Basics, but Wait! There’s more!
Basic UIImagePickerController
is relatively easy to implement, but there are a lot of issues it leaves hanging:
- Hitting no for privacy settings for accessing the camera or library
- Dealing with more than one popover
- Customizing and adding more controls for the camera
- Adding and playing video
- Using and storing pictures
- Using a UIScrollView to zoom and scroll around a picture.
- Getting rid of the memory warnings
- Wanting more power over my camera controls
- Sharing pictures with my friends
Many of these could be questions that can be answered in context with the camera app, or outside of that context. In other lessons I’ll be answering them both ways.
There’s a episode of iOS Development tips weekly that show you how to do this on an iPad playground. iOS Development tips weekly is a video series you can find at the Lynda.com and LinkedIn Learning libraries. The first week of a week’s tip will be available to the public. After that, you will need a subscription to get access to it. Click the image the left to view. You’ll find a transcript here on the website.
The Whole Code
// // ViewController.swift // SwiftPizzaCam // // Created by Steven Lipton on 6/28/16. // Copyright © 2016 Steven Lipton. All rights reserved. // import UIKit class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { let picker = UIImagePickerController() @IBOutlet weak var myImageView: UIImageView! @IBAction func photoFromLibrary(_ sender: UIBarButtonItem) { picker.allowsEditing = false picker.sourceType = .photoLibrary picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)! picker.modalPresentationStyle = .popover present(picker, animated: true, completion: nil) picker.popoverPresentationController?.barButtonItem = sender } @IBAction func shootPhoto(_ sender: UIBarButtonItem) { if UIImagePickerController.isSourceTypeAvailable(.camera) { picker.allowsEditing = false picker.sourceType = UIImagePickerControllerSourceType.camera picker.cameraCaptureMode = .photo picker.modalPresentationStyle = .fullScreen present(picker,animated: true,completion: nil) } else { noCamera() } } func noCamera(){ let alertVC = UIAlertController( title: "No Camera", message: "Sorry, this device has no camera", preferredStyle: .alert) let okAction = UIAlertAction( title: "OK", style:.default, handler: nil) alertVC.addAction(okAction) present( alertVC, animated: true, completion: nil) } override func viewDidLoad() { super.viewDidLoad() picker.delegate = self } //MARK: - Delegates func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { var chosenImage = UIImage() chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2 myImageView.contentMode = .scaleAspectFit //3 myImageView.image = chosenImage //4 dismiss(animated:true, completion: nil) //5 } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) } }
Leave a Reply