How to Make Xib-Based Custom UIImagePickerController Cameras in Swift

Photo Nov 04, 9 16 35 AM

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.

2015-11-04_07-01-51

You’ll get something like this:

2015-11-04_06-42-55

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.

Photo Nov 04, 9 04 48 AM

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.

Photo Nov 04, 9 40 06 AM

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.

2015-11-04_07-17-42

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.

2015-11-04_08-56-46

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.

Photo Nov 04, 9 02 07 AM

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)
    }
}

34 Replies to “How to Make Xib-Based Custom UIImagePickerController Cameras in Swift”

  1. Is it possible to use an overlay with this method, but keeping standard camera controls ? I tried and it does not work. If I just have the label on the xib with standard controls, when I take the picture, picture move a little bit down and the screen freezes, I can’t tap on “retake” or “Use Photo”…

    1. Think of the overlay as a transparent layer covering over the camera controls. Oddly, zoom and focus work well, but all the buttons don’t.. The code as written here has an overlay view the same size as the camera view. The overlay is still a view, so you might try making a view smaller than the camera view. give the height as some percentage of the camera view and the y position below the upper buttons. I haven’t tried this, but it may work.

      1. I have the same problem. Resizing didn’t helped.
        Since I want to have my own button on the overlay, i can’t uncheck the “User Interaction Enabled” box.

    2. I experienced this behavior as well. I fixed it by going to the .xib file for CustomOverlayViewController, clicking on it’s View, going to the Attributes Inspector and under the Interaction section I unchecked the “User Interaction Enabled” box. Now it works as intended. Hope that helps!

  2. Is it possible to use this method with standard controls enabled ? It does not work for me. If I just have the label on the xib and I let standard controls, when I take the picture, the screen freezes and I can’t tap on “retake” or “Use photo”. Thanks for your help.

  3. Hello Steven,

    Very helpful tutorial somehow i’m getting error when i click on shoot the picture. Maybe i’m doing something wrong. Is it possible for you to upload the code on github. I really need to get this working for my project

  4. I get following error while click on “shoot photo” button

    Could not cast value of type ‘UIView’ (0x3ae59a78) to ‘CustomCamera.CustomOverlayView’ (0xcd1e8).

    1. sorry Steven Lipton i solve this error but camera not starting and if i am click on shoot photo button image not take from camera. hot to start camera and take a picture. please help me.

      Thanks a lot…!!!!

  5. Well done. Thank you very much, this was super helpful. Only mix up was I put code into CustomOverlayViewController instead of CustomOverlayView. Works well. Appreciate you providing your expertise in this tutorial. Many thanks.

  6. Hey Steven, thanks so much for the tutorial! I’ve made a custom overlay video camera with these instructions, but for some reason when I click on the record button again to stop recording, the preview of the video I just recorded doesn’t show up. The code never gets to the “imagePickerController( picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])” function.

    These are the related parts from my ViewController.swift file:

    func didRecord(overlayView: CustomOverlayView) {
    if (recordingStatus) {
    recordingStatus = false
    picker.stopVideoCapture()
    } else {
    recordingStatus = true
    picker.startVideoCapture()

    }
    }

    _______________________________________________________

    @IBAction func takeVideo(sender: UIButton) {
    startCameraFromViewController(self, withDelegate: self)
    }

    func startCameraFromViewController(viewController: UIViewController, withDelegate delegate: protocol) {

    // Video Recording Properties
    if UIImagePickerController.isSourceTypeAvailable(.Camera) == true {
    picker = UIImagePickerController() // Make a clean controller
    picker.allowsEditing = true
    picker.sourceType = .Camera
    picker.mediaTypes = [kUTTypeMovie as NSString as String]
    picker.showsCameraControls = false
    picker.delegate = delegate
    picker.videoMaximumDuration = 5 // Replace w/ NSTimer
    picker.videoQuality = UIImagePickerControllerQualityType.TypeHigh

    // Overlay Setup
    let customViewController = CustomOverlayViewController(
    nibName: “CustomOverlayViewController”,
    bundle: nil
    )

    // Using the view for outlets and actions, view controller is just a reference
    let customView: CustomOverlayView = customViewController.view as! CustomOverlayView
    // Makes the video capture screen use this frame instead
    customView.frame = self.picker.view.frame
    customView.delegate = self
    // customView.userInteractionEnabled = false

    picker.modalPresentationStyle = .FullScreen
    presentViewController(picker,
    animated: true, completion: {
    // Upon completion of loading the picker, load customView as the overlay
    self.picker.cameraOverlayView = customView
    }
    )

    // presentViewController(picker, animated: true, completion: nil)

    } else { // No camera found

    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)
    }
    }

    ____________________________________________________

    And then inside my CustomOverlayView.swift file I have:

    var delegate: CustomOverlayDelegate! = nil

    @IBAction func recordButton(sender: UIButton) {
    delegate.didRecord(self)
    exitIcon.hidden = true
    flipIcon.hidden = true
    if (cameraOrientation == false) {
    flashIcon.hidden = true
    }
    uploadIcon.hidden = true
    progressView.alpha = 1.0
    UIView.animateWithDuration(5, animations: { () -> Void in
    self.progressView.setProgress(1.0, animated: true)
    })

    }

    Do you have any ideas as to why this is happening? I could always send over the actual code if this is hard to read (which it probably is).

    Thanks so much for your help!

    1. Update: It’s actually getting to the didFinishPickingMediaWithInfo function, but the preview of the video never appears. Is the overlay somehow preventing me from seeing the preview?

  7. Hi Steven,

    Thanks so much for this tutorial.
    When I take a photo, the picture goes down a little bit but my overlay stays in the middle of the screen. I really need to have the overlay and the picture “synchronised”. Do you know how to do it ?

  8. Hi steven,

    I encounter an error when trying to add the delegate “CustomOverlayDelegate” to the view controller.

    Error says : Type ‘ViewController’ does not conform to protocol ‘CustomOverlayDelegate’

    When I open the error it says : Protocol requires function ‘did Cancel’ with type ‘(CustomOverlayView) -> ()’

    Protocol requires function ‘did Shoot’ with type ‘(CustomOverlayView) -> ()’

    I’m using Xcode 8

      1. Found the issue ! Thx for the heads up !!
        Would you know what could make the custom buttons to not wok ?
        They are not firing whatever I try

      2. I think there one more thinkg I’m issing, but here’s one: Make sure they are on the overlay, not the view. Otherwise, the overlay blocks the input of the buttons.

  9. Yes there are. I’m trying to understand how the accessibility and user interaction parameters (Identity and Attributes inspectors) works too. I think that’s what is making my buttons to not fire :(

    Thanks so much for your help man

  10. When I try to change the shootButton’s text from the ViewController,it isn’t changing. There are no errors,and the line is surely executing(I know as I debugged it). I also put an UIImageView on the overlay and tried to change it’s image from ViewController,but it isn’t changing as well. Can you help me on this?

      1. My main problem was when I was trying to change any text or image, I was creating another CustomOverlayView reference and a new picker(I am dumb). So when I was changing any text or image,it was changing but it didn’t show as previous picker was still on the view. Using one picker and one CustomOverlayView reference did the trick.

  11. I am trying to use the code above in my project.
    But I get this error:
    unexpectedly found nil while unwrapping an Optional value

    it happen just after I delegate the controls:

    @IBAction func cancelButton(_ sender: UIButton) {
    print(“cancel1”)
    delegate.didCancel(overlayView: self)
    }

    I see the “cancel1” print, but then the program crash.

    How can I debug it?

    Thanks
    Dudu

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s