Second part of the Custom Camera App series.
- Custom Camera App – Part 1: Custom Overlay
- Custom Camera App – Part 2: Taking Pictures
- Custom Camera App – Part 3: Assets Library
In the first part of this series, we explained how to create the custom overlay for a custom camera app.
However, if you tried to compile the code it must have produced many errors. That is because you still need to implement the methods called from the overlay in your CameraViewController.
Today we will implement all those methods and make our app capable of taking pictures and accessing the photo library.
Step 1
First of all, you need to configure the view that will be displayed when the user selects a photo from the library. Therefore, go to the storyboard, select the View Controller that appears by default, and change its class to CameraViewController in the Identity Inspector.
Then, drag a Navigation Bar from the Object library and add two Bar Button Items to it. Finally, add a Scroll View. It should look like this:
Step 2
Now you have to create two IBAction properties for the two buttons in the Navigation Bar, and an IBOutlet for the Scroll View. Moreover, you need to declare the methods used in the View Controller, the ones we called from our Custom Overlay in the first part of the series.
#import <UIKit/UIKit.h>
@interface CameraViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIScrollViewDelegate> {
IBOutlet UIScrollView *scrollView;
UIImageView *imageView;
}
@property (nonatomic, strong) UIImagePickerController *picker;
- (IBAction) backButton:(id)sender;
- (IBAction) doneButton:(id)sender;
- (void) changeFlash:(id)sender;
- (void) changeCamera;
- (void) showLibrary;
- (void)showCamera;
- (void)takePicture;
@end
Step 3
Before we start with the implementation of those methods, go to the storyboard again. Control-click each of the Navigation Bar Items, and drag them to the Camera View Controller. When prompted, choose backButton for the left button, and doneButton for the right one.
For the Scroll View, Control-click Camera View Controller and drag it to the Scroll View. Select scrollView.
Step 4
You are going to need a variable for the overlay, since you will use it in different methods, and a boolean to control when the user is choosing an image from the camera roll and he decides to cancel.
#import "CameraViewController.h"
#import "CustomOverlayView.h"
#define CAMERA_TRANSFORM_X 1
#define CAMERA_TRANSFORM_Y 1
// Screen dimensions
#define SCREEN_WIDTH 320
#define SCREEN_HEIGTH 480
@implementation CameraViewController
{
CustomOverlayView *overlay;
BOOL didCancel;
}
@synthesize picker;
- (void)viewDidLoad
{
[super viewDidLoad];
overlay = [[CustomOverlayView alloc]
initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGTH)];
overlay.delegate = self;
self.picker = [[[UIImagePickerController alloc] init];
self.picker.delegate = self;
self.picker.navigationBarHidden = YES;
self.picker.toolbarHidden = YES;
self.picker.wantsFullScreenLayout = YES;
[self showCamera];
}
@end
Step 5
The method showCamera is not very difficult to implement. You only need to set the source of the UIImagePickerController to UIImagePickerControllerSourceTypeCamera, set some more attributes, and finally present the picker:
- (void) showCamera
{
self.picker.sourceType = UIImagePickerControllerSourceTypeCamera;
// Must be NO.
self.picker.showsCameraControls = NO;
self.picker.cameraViewTransform =
CGAffineTransformScale(self.picker.cameraViewTransform, CAMERA_TRANSFORM_X, CAMERA_TRANSFORM_Y);
// When showCamera is called, it will show by default the back camera, so if the flashButton was
// hidden because the user switched to the front camera, you have to show it again.
if (overlay.flashButton.hidden) {
overlay.flashButton.hidden = NO;
}
self.picker.cameraOverlayView = overlay;
// If the user cancelled the selection of an image in the camera roll, we have to call this method
// again.
if (!didCancel) {
[self presentModalViewController:self.picker animated:YES];
} else {
didCancel = NO;
}
}
Step 6
Now we need to implement all the other methods, which are really short. To take a picture, we only need to call the UIImagePickerController‘s method takePicture. To allow the user to zoom the selected image in the Scroll View, we also need to implement the viewForZoomingInScrollView:(UIScrollView *)scrollView method.
- (void)takePicture
{
[picker takePicture];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
didCancel = YES;
[self showCamera];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return imageView;
}
- (IBAction) backButton:(id)sender
{
self.picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
[self presentModalViewController:self.picker animated:YES];
}
- (IBAction)doneButton:(id)sender
{
self.picker.sourceType = UIImagePickerControllerSourceTypeCamera;
if (overlay.flashButton.hidden) {
overlay.flashButton.hidden = NO;
}
[self presentModalViewController:self.picker animated:YES];
}
- (void) changeFlash:(id)sender
{
switch (self.picker.cameraFlashMode) {
case UIImagePickerControllerCameraFlashModeAuto:
[(UIButton *)sender setImage:[UIImage imageNamed:@"flash01"] forState:UIControlStateNormal];
self.picker.cameraFlashMode = UIImagePickerControllerCameraFlashModeOn;
break;
case UIImagePickerControllerCameraFlashModeOn:
[(UIButton *)sender setImage:[UIImage imageNamed:@"flash03"] forState:UIControlStateNormal];
self.picker.cameraFlashMode = UIImagePickerControllerCameraFlashModeOff;
break;
case UIImagePickerControllerCameraFlashModeOff:
[(UIButton *)sender setImage:[UIImage imageNamed:@"flash02"] forState:UIControlStateNormal];
self.picker.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto;
break;
}
}
- (void)changeCamera
{
if (self.picker.cameraDevice == UIImagePickerControllerCameraDeviceFront) {
self.picker.cameraDevice = UIImagePickerControllerCameraDeviceRear;
overlay.flashButton.hidden = NO;
} else {
self.picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
overlay.flashButton.hidden = YES;
}
}
- (void)showLibrary
{
self.picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
Step 7
Finally, we need to do something when the user has taken a picture. UIImagePickerController has a method for this, which is called whenever the user takes a picture or selects a photo from the library. In this method, we first need to check whether he took a new photo, or if he chose one from the camera roll. In the first case, we only need to save the photo and reenable pictureButton.
In the second case, we need to put the picture in the Scroll View, and dismiss the UIImagePickerController. The way I set up the image in the Scroll View is from a project from the WWDC 2010 to display images much nicer in Scroll Views.
- (void) imagePickerController:(UIImagePickerController *)aPicker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *aImage = (UIImage *)[info objectForKey:UIImagePickerControllerOriginalImage];
if (aPicker.sourceType == UIImagePickerControllerSourceTypeCamera) {
UIImageWriteToSavedPhotosAlbum (aImage, nil, nil , nil);
overlay.pictureButton.enabled = YES;
} else {
// clear the previous imageView
[imageView removeFromSuperview];
imageView = nil;
// reset our zoomScale
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
imageView = [[[UIImageView alloc] initWithImage:aImage];
scrollView.contentSize = aImage.size;
scrollView.bounces = NO;
scrollView.delegate = self;
// set up our content size and min/max zoomscale
CGFloat xScale = applicationFrame.size.width / aImage.size.width; // the scale needed to perfectly fit the image width-wise
CGFloat yScale = applicationFrame.size.height / aImage.size.height; // the scale needed to perfectly fit the image height-wise
CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible
// on high resolution screens we have double the pixel density, so we will be seeing every pixel if we limit the
// maximum zoom scale to 0.5.
CGFloat maxScale = 1.0 / [[UIScreen mainScreen] scale];
// don't let minScale exceed maxScale. (If the image is smaller than the screen, we don't want to force it to be zoomed.)
if (minScale > maxScale) {
minScale = maxScale;
}
scrollView.contentSize = aImage.size;
scrollView.maximumZoomScale = maxScale;
scrollView.minimumZoomScale = minScale;
scrollView.zoomScale = minScale;
//////////////
CGSize boundsSize = applicationFrame.size;
CGRect frameToCenter = imageView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
//////////////
imageView.frame = frameToCenter;
[scrollView addSubview:imageView];
[self.picker dismissModalViewControllerAnimated:YES];
}
}
Conclusion
The app is almost ready, there is only one last thing we need to implement. As you might have noticed, we didn’t use any image for the lastPicture button. I want to show you in the third and final part of the series how to access the device images from your code using ALAssets library, so we will use a thumbnail of the last picture taken as the image for that button. However, you can still access the library if you click on the bottom left corner of the app.