custom UIImagePickerController camera view

January 12, 2009

WARNING: While there are many apps (including some of mine) that use this technique, you should know some new apps and updates to existing apps have been rejected recently (april/2009). Please read the comments. So far I don’t know of any instances where the developer successfully argued Apple’s decision if the app was rejected.

With all that said, there’s still an outpour of apps that use this technique… so the decision is your.

Here is some information about inspecting and customizing the UIImagePickerController camera view. You can download the working xcode project with all the source code here: customImagePicker

I wanted to remove the top part of the interface (gray bacground and “Take Photo” label) forĀ  Mean Photo and Nice Photo (version 1.2+). There is also a very annoying image shift between the camera view and the preview that I thought would be nice to fix.

You can download the customImagePicker xcode project with all the source code that demonstrates these techniques. Run it on your iPhone in debug mode and check the log output for information.

CustomImagePicker is a subclass of UIImagePickerController. I override the viewDidLoad: method. When this method is called the view is ready to be customized (this approach was suggested in netsharc’s post here.)

-(void) viewDidAppear: (BOOL)animated {
	// make sure to call the same method on the super class
	[super viewDidAppear:animated];

	/* ... customize view here ... */
}

The inspectView:: method to recursively walks the view hierarchy and prints information about the views to the log: the class descriptions, the position and size and the path (talk about the path in a bit.) If you are interested in other information about the views, you can extend this method to print them. Here’s how this method is invoked:

[self inspectView:self.view depth:0 path:@""];

You can uncomment this line in viewDidLoad:, but it’s also called from previewCheck every five seconds, so you can monitor the view hierarchy as it changes in the different states. So for example if you are interested what the view hierarchy is like in the preview mode, just take a photo and wait until the next dump appears in the log. The output looks something like this:

 .description: UILayoutContainerView: 0x125b90
 .frame: 0, 0, 320, 460
 .subviews: 2

 --subview-- /0
   .description: UITransitionView: 0x1205a0
   .frame: 0, 0, 320, 460
   .subviews: 1

   --subview-- /0/0
     .description: UIView: 0x125da0
     .frame: 0, 0, 320, 460
     .subviews: 1

     --subview-- /0/0/0
       .description: PLCameraView: 0x125e50
       .frame: 0, 0, 320, 460
       .subviews: 4

       --subview-- /0/0/0/0
         .description: UIView: 0x1264c0
         .frame: 0, 0, 320, 427
         .subviews: 0

       --subview-- /0/0/0/1
         .description: UIImageView: 0x128850
         .class: UIImageView
         .frame: 10000, 10000, 320, 480
         .subviews: 0

       --subview-- /0/0/0/2
         .description: UIView: 0x11b200
         .frame: 0, 0, 320, 33
         .subviews: 0

       --subview-- /0/0/0/3
         .description: PLCropOverlay: 0x127dc0
         .frame: 0, 0, 320, 460
         .subviews: 3

         --subview-- /0/0/0/3/0
           .description: UIImageView: 0x12b2f0
           .class: UIImageView
           .frame: 0, 0, 320, 96
           .subviews: 0

         --subview-- /0/0/0/3/1
           .description: PLCropLCDLayer: 0x12b350
           .frame: 0, 0, 320, 96
           .subviews: 0

         --subview-- /0/0/0/3/2
           .description: TPBottomDualButtonBar: 0x12b5b0
           .frame: 0, 0, 320, 96
           .subviews: 2

           --subview-- /0/0/0/3/2/0
             .description: TPPushButton: 0x12ba40
             .frame: 22, 22, 128, 47
             .subviews: 0

           --subview-- /0/0/0/3/2/1
             .description: TPCameraPushButton: 0x12df10
             .frame: 170, 170, 128, 47
             .subviews: 1

             --subview-- /0/0/0/3/2/1/0
               .description: UIImageView: 0x12e960
               .class: UIImageView
               .frame: 51, 51, 26, 19
               .subviews: 0

 --subview-- /1
   .description: UINavigationBar: 0x125a30
   .frame: 0, 0, 320, 44
   .subviews: 1

   --subview-- /1/0
     .description: UINavigationItemView: 0x11abe0
     .frame: 160, 160, 0, 27
     .subviews: 0

Confusing? It’s actually pretty simple (although not very pretty.)

Next to each subview you can see it’s path. For example /0/0/0/3 means that it is subview #3 of subview #0 of subview #0 of subview #0. To look it up in the view hierarchy, just do this:

    UIView *theView = [[[[[[[[self.view subviews] objectAtIndex:0]
                                        subviews] objectAtIndex:0]
                                        subviews] objectAtIndex:0]
				        subviews] objectAtIndex:3];

See the numbers next to objectAtIndex:? It’s /0/0/0/3… the path… So now you can look up any UIView from the hierarchy and modify it!

Even if the description shows that the class is part of the private iPhone libraries (eg. PLCropLCDLayer), it must be a subclass of UIView to be in the hierarchy. We don’t know (or at least not supposed to know) what methods the private library classes have, but they do have all the methods and properties of UIView. So we can make theView transparent or hidden like this (this is actually the path for the UI above the camera preview):

    [theView setAlpha:0.5];    // semi transparent
    [theView setHidden:YES];    // hidden

I wanted to get rid of the gray bar and “Take Photo” label on top. The path for the gray background is /0/0/0/3/0, the label is /0/0/0/3/1. I look these up and animate their opacity (alpha) to 0:

    UIImageView *overlay = [[[[[[[[[[self.view subviews] objectAtIndex:0]
                                               subviews] objectAtIndex:0]
                                               subviews] objectAtIndex:0]
                                               subviews] objectAtIndex:3]
                                               subviews] objectAtIndex:0];

    UIView *label = [[[[[[[[[[self.view subviews] objectAtIndex:0]
                                        subviews] objectAtIndex:0]
                                        subviews] objectAtIndex:0]
                                        subviews] objectAtIndex:3]
                                        subviews] objectAtIndex:1];

    // animate their visibility (alpha) to 0 with a simple UIView animation
    //
    [UIView beginAnimations:nil context:nil];
    [overlay setAlpha:0.0];
    [label setAlpha:0.0];
    [UIView commitAnimations];

Here’s what it looks like in Nice Photo. The LOVE graphics is added in a separate view on top of the camera view.

nicephotocamera

Nice Photo App with Camera UI tweaked

The camera view is /0/0/0/0. To make it semi transparent (not sure why you would do this, but shows how to look it up):

    UIView *cameraView = [[[[[[[[self.view subviews] objectAtIndex:0]
                                           subviews] objectAtIndex:0]
                                           subviews] objectAtIndex:0]
                                           subviews] objectAtIndex:0];
    [cameraView setAlpha:0.5];

You might be interested in button push events. The buttons are subclasses of UIControl, so you can easily add an action to them. For example to add an action to the camera button (TPCameraPushButton at /0/0/0/3/2/1):

    UIControl *captureButton = [[[[[[[[[[[[self.view subviews] objectAtIndex:0]
                                                     subviews] objectAtIndex:0]
                                                     subviews] objectAtIndex:0]
                                                     subviews] objectAtIndex:3]
                                                     subviews] objectAtIndex:2]
                                                     subviews] objectAtIndex:1];

    [captureButton addTarget:self action:@selector(captureButtonAction:)
        forControlEvents:UIControlEventTouchUpInside];

(The captureButtonAction: method prints a message to the log when you tap the camera button in customImagePicker.)

I wanted to fix the shift between the preview and the camera view. To do this, I have a timer calling the previewCheck method (I’m pretty sure there’s more elegant ways… but come ont, timers are cool!). The preview will be added to the UIView at /0/0/0/2. By default this view has no subviews, but subviews are added when in preview mode. Then I modify the transform of /0/0/0/2/0/0/0 like such (btw this only makes sense in portrait mode):

    [previewImage setTransform:CGAffineTransformTranslate(
        CGAffineTransformMakeScale(320.0/1200, 320.0/1200),
                                   -12.0*1200/320,-17.0*1200/320)];

(Now you probably know why I was so interested in the view sizes… the view frames for the preview image are completely bizarre… hence the image shift. Are people allowed to drink on the job at Apple?)

You can experiment with other views. The inspectView method gives you a pretty good map of the hierarchy and it also works for other source types (like picking an image from the photo library.) Please look at the customImagePicker xcode project, I added lots of comments (more than what I retyped here ;).

As always, feel free to contact me… and please check out www.meanvsnice.com for some shots taken with Mean Photo and Nice Photo.

107 Responses to “custom UIImagePickerController camera view”

  1. @Jimbob: showCameraControls = NO should not hide the preview, In fact the preview is the only view visible.

  2. Lajos,

    Awesome! I have been looking for a method of doing custom camera controls for UIImagePickerController. I downloaded the project, and tested with a 3Gs, but only the normal controls show. I am on the newest version of xcode targeting the 3.0 SDK.

    Does this approach still work with the 3.0 SDK?

    best wishes,

    ty

  3. @ty: In 3.0 the view hierarchy constantly changes. This project only works with pre 3.0 SDKs. In 3.0 you can print the view hierarchy and use that info to figure out where specific views are. But again, since the view hierarchy changes, it’s a bit trickier than pre 3.0. You should also look at the UIImagePickerController documentation, in 3.0 there are new methods to hide the camera UI, insert view above the camera UI and take photo.

  4. Ok, I’ve have tried capturing the preview screen (targeting 4.0) and your code works great to dump the classes. as a side note, I’ll probably be using that for other troubleshooting in the future.

    I am still wanting to use this method as opposed to the UIGetScreenImage() method because I don’t want to capture any of the overlay content. But…all I ever get is the shutter screen and it is driving me nuts!!

    Here is the code I’m using. The path 0/0/0/0/2 maps to the UIImageView.

    //Crawl CameraPickerController for Image preview
    UIView *preview = [[[[[[[[[[[thePicker.view subviews] objectAtIndex:0]
    subviews] objectAtIndex: 0]
    subviews] objectAtIndex: 0]
    subviews] objectAtIndex: 0]
    subviews] objectAtIndex: 2] retain];

    //Turn the view into an image
    UIGraphicsBeginImageContext(preview.bounds.size);
    [preview.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    NSData *imgData=UIImageJPEGRepresentation(viewImage ,0);
    viewImage = nil;

    [preview release];
    preview = nil;

  5. @Ryan: 4.0 gives you full access to the camera stream with the AVFoundation framework. You don’t need any tricks to access the camera stream anymore.

  6. @lajos : does that mean you can put other buttons such as “cancel”,”setting” etc ?

  7. @Forrest: Yes, you should have pretty much full control over the camera. Between AVFoundation and UIImagePickerController, you should be able to show live preview, take pictures, start video recording, and even get frames out of the video stream.

Leave a Reply