Apps with Cameras are cool. Most of us do not have the time to spend writing the code to use AVFoundation
to get a camera app. For most uses, the UIImagePickerController
is a much better option for a camera. While a handy camera API, it is often limited in its out of the box incarnation. You can customize it though. For many, customization is not easy, since usually it takes making views and controls programmatically to get it to work. But there is an easier way. In this lesson, we’ll explore how to make one using Interface builder.
The UIPickerView
has a property called cameraOverlayView
. This is a a view that is on top of the view usually displayed by the camera. Developers can customize this view. You can add images or drawing to it to make grids or other references for your camera, or you can add new control as like buttons and labels. If you add more buttons, you might also want to shut down the camera’s controls, and there is a property for that showsCameraControls
. We’ll also use the method takePicture
to take the picture. An advantage to a custom views is you can take multiple photos, while the standard controls only allow for one photo.
Make a Camera App
We’ll need a camera app to start. I’ll assume you know how to set up an image picker for a camera. I’m just going to post code for it here. For an explanation of this code, you can see the tutorial on UIImagePickerView
. We’ll only make a camera in this case and directly save photos to the photo album.
Make a new project called CustomCameraDemo using Swift. Go to the story board and add a single button titled Shoot the picture
. Using auto layout’s align menu, center the button on the view.
You’ll get something like this:
Open the assistant editor and control-drag to make an action named shootPhoto. That’s all the layout we’ll do on the view controller. Close the assistant editor and go to the ViewController.swift class. Change the class method to add two delegates for the picker:
class ViewController: UCustomOverlayViewIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
Add the picker as a property
var picker = UIImagePickerController()
Add to the shootPhoto
method the following code:
@IBAction func shootPhoto(sender: UIButton) { if UIImagePickerController.availableCaptureModesForCameraDevice(.Rear) != nil { picker = UIImagePickerController() //make a clean controller picker.allowsEditing = false picker.sourceType = UIImagePickerControllerSourceType.Camera picker.cameraCaptureMode = .Photo picker.showsCameraControls = true picker.modalPresentationStyle = .FullScreen presentViewController(picker,animated: true,completion: nil) } else { //no camera found -- alert the user. 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) presentViewController( alertVC, animated: true, completion: nil) } }
This sets up the camera if there is a rear facing camera, and displays an alert if there is not one. According to Apple’s recommendations, the camera will be a full screen modal. Add the necessary delegate methods to the code:
//MARK: Picker Delegates func imagePickerController( picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2 UIImageWriteToSavedPhotosAlbum(chosenImage, self,nil, nil) } //What to do if the image picker cancels. func imagePickerControllerDidCancel(picker: UIImagePickerController) { dismissViewControllerAnimated(true, completion: nil) }
Our code will save the photo to the photo album directly, just to keep things simple. Camera apps require a real device to work properly, so connect up a device to your Xcode system, and select the device in Xcode. Build and run. After answering the permission to use the camera question, you should have a working camera app.
Adding a Xib to the Overlay View
With a working camera, let’s add the overlay view. Press Command-N in Xcode and create new Cocoa Touch class subclassing UIViewController
. Check the box for Also create xib file. Name the class CustomOverlayViewController. Be sure to save it in the same group as the ViewController.swift file. Press Command-N and add another class, this time called CustomOverlayView subclassing UIView
.
Go to the CustomOverlayController.xib you just created. Open the document outline and select the view. Once selected, go to the identity inspector, and set the class for the view to CustomOverlayView. Add to the xib a label with the Text My Photo. Just like the storyboard, using the auto layout align menu center the label vertically and horizontally in the view.
Go to the ViewController
class again. Between the picker.delegate
and picker.modalPresentationStyle
assignments add the following code:
//customView stuff let customViewController = CustomOverlayViewController( nibName:"CustomOverlayViewController", bundle: nil ) let customView:CustomOverlayView = customViewController.view as! CustomOverlayView customView.frame = self.picker.view.frame
This is where the real trick happens. We will not be using the view controller, but the view for the outlets and actions. The view controller will act as a reference to the view, but little more than that. We get the view controller, then the view as customView
. Our last line makes sure the frame is the same for both customView
and the modal view of the picker. We need to add the view to the picker. However, it’s best to add the view after we display it. We’ll add the view in the completion:
closure to present the controller. Change the current presentViewController
to this:
presentViewController(picker, animated: true, completion: { self.picker.cameraOverlayView = customView } )
Build and run, then select the camera. If all runs well, then your camera will be obscured by the xib’s white background.
Whenever using an overlay, make sure the background is clearColor
. Stop the app and change the background of the xib to clear. Run again and you get your text in the middle of your photo.
Using outlets and actions on the overlay
Go back to the xib and open the assistant editor. Click on the view in the document outline. Using the assistant editor menus, select the CustomOverlayView.swift file.
Control drag from the label to the CustomOverlayView
class making an outlet named cameraLabel. We ignore the view controller and put our outlet in the view. Switch over to ViewController
class and add the following after the customView.frame
assignment:
customView.cameraLabel.text = "Hello Cute Camera"
Build and run.
The time the text reflects our assignment to the outlet.
Adding actions is just as easy, but with a twist. Add two buttons to the xib. Title one button Shoot and the other button Cancel. Give both buttons a gray background. Using autolayout, Pin the Shoot button 0 left and 20 down with a width of 100 and a height of 100. Pin the Cancel button 0 right and 20 down with a width of 100 and a height of 100.
Control-drag from the Shoot button and add an action called shootButton to the CustomOverlayView
class . Control-drag from the Cancel button and add an action called cancelButton. Change the code for the buttons to this:
@IBAction func cancelButton(sender: UIButton) { cameraLabel.text = "I want to exit" } @IBAction func shootButton(sender: UIButton) { cameraLabel.text = "Even Cooler Camera" }
These buttons will conflict with the default buttons. Shut off the camera controls by changing showsCameraControls
to false
in the
ViewController
class:
picker.showsCameraControls = false
Build and run. You will have two buttons that change the label, but no way to take a picture — or exit. Stop the app in Xcode.
Adding Delegates to the View
We need to notify the view controller that the button was pressed. There are several ways to do this, from notifications to delegates. I prefer delegates. In the CustomOverlayView.swift file, add the following protocol above the class declaration:
protocol CustomOverlayDelegate{ func didCancel(overlayView:CustomOverlayView) func didShoot(overlayView:CustomOverlayView) }
Then add the protocol as a delegate to the CustomOverlayView
class
var delegate:CustomOverlayDelegate! = nil
This is still a view, not a controller. I want to do as little processing as possible in the view, and save it for the controller. Our actions will do nothing more than call the delegate methods in the protocol. Change our two actions to this:
@IBAction func cancelButton(sender: UIButton) { cameraLabel.text = "I want to exit" delegate.didCancel(self) } @IBAction func shootButton(sender: UIButton) { cameraLabel.text = "Even Cooler Camera" delegate.didShoot(self) }
The code in our view will execute the delegate methods adopted by ViewController
. Go to ViewController
and adopt the delegate
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, CameraOverlayDelegate {
Add the following delegate methods to ViewController
func didCancel(overlayView:CustomOverlayView) { picker.dismissViewControllerAnimated(true, completion: nil) } func didShoot(overlayView:CustomOverlayView) { picker.takePicture() overlayView.cameraLabel.text = "Shot Photo" }
The UIImagePickerController
has a method called takePicture
which takes the picture.
Set the delegate in our property setting for customView
in shootPhoto
customView.delegate = self
Build and run. You have a working camera with your own controls.
This is the basics of a camera overlay. There are a few things you could improve the camera. Currently the camera only works in portrait. More code would be need to rotate the overlay view. Icons like I use in the MiPlatform 2.0 App shown above (available mid-November) instead of titles make for a better camera experience. You can add more labels or views to better indicate what is going on in the viewer. For most applications this might be enough to get the camera experience you need.
The Whole Code
I did not include CustomOverlayViewController.Swift in this, since we didn’t code anything there, but you do need it for this code to work
ViewController.swift
// // ViewController.swift // CustomCameraDemo // // Created by Steven Lipton on 11/4/15. // Copyright © 2015 MakeAppPie.Com. All rights reserved. // import UIKit class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, CustomOverlayDelegate { var picker = UIImagePickerController() //MARK: - Actions @IBAction func shootPhoto(sender: UIButton) { if UIImagePickerController.availableCaptureModesForCameraDevice(.Rear) != nil { picker = UIImagePickerController() //make a clean controller picker.allowsEditing = false picker.sourceType = UIImagePickerControllerSourceType.Camera picker.cameraCaptureMode = .Photo picker.showsCameraControls = false //picker.delegate = self //uncomment if you want to take multiple pictures. //customView stuff let customViewController = CustomOverlayViewController( nibName:"CustomOverlayViewController", bundle: nil ) let customView:CustomOverlayView = customViewController.view as! CustomOverlayView customView.frame = self.picker.view.frame customView.cameraLabel.text = "Hello Cute Camera" customView.delegate = self //presentation of the camera picker.modalPresentationStyle = .FullScreen presentViewController(picker, animated: true, completion: { self.picker.cameraOverlayView = customView }) } else { //no camera found -- alert the user. 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) presentViewController( alertVC, animated: true, completion: nil) } } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } //MARK: - Delegates //MARK: Image Picker Controller Delegates func imagePickerController( picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //get the image from info UIImageWriteToSavedPhotosAlbum(chosenImage, self,nil, nil) //save to the photo library } //What to do if the image picker cancels. func imagePickerControllerDidCancel(picker: UIImagePickerController) { dismissViewControllerAnimated(true, completion: nil) print("Canceled!!") } //MARK: Custom View Delegates func didCancel(overlayView:CustomOverlayView) { picker.dismissViewControllerAnimated(true, completion: nil) print("dismissed!!") } func didShoot(overlayView:CustomOverlayView) { picker.takePicture() overlayView.cameraLabel.text = "Shot Photo" print("Shot Photo") } }
CustomOverlayView.swift
// // CustomOverlayView.swift // CustomCameraDemo // // Created by Steven Lipton on 11/4/15. // Copyright © 2015 MakeAppPie.Com. All rights reserved. // import UIKit protocol CustomOverlayDelegate{ func didCancel(overlayView:CustomOverlayView) func didShoot(overlayView:CustomOverlayView) } class CustomOverlayView: UIView { @IBOutlet weak var cameraLabel: UILabel! var delegate:CustomOverlayDelegate! = nil @IBAction func cancelButton(sender: UIButton) { cameraLabel.text = "I want to exit" delegate.didCancel(self) } @IBAction func shootButton(sender: UIButton) { cameraLabel.text = "Even Cooler Camera" delegate.didShoot(self) } }
Leave a reply to Steven Lipton Cancel reply