Rafael
Rafael

Reputation: 388

Presenting camera roll UIImagePickerController over open camera

On my app I have a cameraOverlayView over my open camera with custom controls for the camera buttons. The app allows the user to take several pictures before closing the camera, so the shutter button does not call dismissViewControllerAnimated, instead there's a close button for when you're done taking pictures.

Now, one of the buttons on the camera overlay is a gallery button to allow the user to pick a saved image instead of shooting a new one. I've tried two different approaches to make this work, both failed.

First approach

Use the same UIImagePickerController instance that is currently presenting the overlay and switch the sourceType to library. It does present the gallery then, but when a photo is tapped, I can't dismiss the galley without dismissing the whole overlay.

Second approach

Create a separate instance of UIImagePickerController, set the sourceType to gallery and attempt to call presentViewController, which then fails with the warning:

"Warning: Attempt to present on whose view is not in the window hierarchy!"

Does anyone have a solution for this issue? Is this even possible?

Upvotes: 10

Views: 20196

Answers (5)

Yang Meyer
Yang Meyer

Reputation: 5719

I present a custom camera view controller, onto which I add a "SourceTypeCamera" UIImagePickerController as a contained child view controller. My custom view controller has a button that in turn adds another UIImagePickerController instance, this one a "SourceTypePhotoLibrary".

You end up with a nested view controller hierarchy:

  • root/main view controller — presents:
  • my custom camera view controller – adds as child view controller:
  • either camera or photo-library UIImagePickerController

Something like this:

- (void)viewDidLoad { (id<UIImagePickerControllerDelegate,UINavigationControllerDelegate>)theDelegate {
    [super viewDidLoad];
    [self startImagePickerWithSourceType:UIImagePickerControllerSourceTypeCamera
                                delegate:self];
    // configure button in camera overlay to call -pickPhotoTapped
}

- (void) pickPhotoTapped {
    [self startImagePickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary
                                delegate:self];
}

- (BOOL) startImagePickerWithSourceType:(UIImagePickerControllerSourceType)sourceType
                               delegate:(id<UIImagePickerControllerDelegate,UINavigationControllerDelegate>)theDelegate {
    if (([UIImagePickerController isSourceTypeAvailable:sourceType] == NO)
            || (theDelegate == nil))
        return NO;

    self.cameraUI = [[UIImagePickerController alloc] init];
    self.cameraUI.sourceType = sourceType;
    self.cameraUI.view.frame = self.view.bounds;
    self.cameraUI.delegate = theDelegate;

    if (sourceType == UIImagePickerControllerSourceTypeCamera) {
        self.cameraUI.allowsEditing = NO;
        self.cameraUI.showsCameraControls = NO;
        self.cameraUI.cameraOverlayView = [self overlayView];
    }

    [self addChildViewController:self.cameraUI];
    [self.view addSubview:self.cameraUI.view];
    [self.cameraUI didMoveToParentViewController:self];

    return YES;
}

Upvotes: 1

Steven Jiang
Steven Jiang

Reputation: 1004

Try this~~ I think it is your goal.

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) UIImagePickerController *imagePicker;

@end

@implementation ViewController

@synthesize imagePicker = _imagePicker;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

}


- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:YES];

    sleep(2);

    _imagePicker = [[UIImagePickerController alloc] init];
    [_imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera];
    [_imagePicker setDelegate:self];

    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 50, 100, 30)];
    [button setTitle:@"Library" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor darkGrayColor]];
    [button addTarget:self action:@selector(gotoLibrary:) forControlEvents:UIControlEventTouchUpInside];

    [_imagePicker.view addSubview:button];

    [self presentViewController:_imagePicker animated:YES completion:nil];
}
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(IBAction)gotoLibrary:(id)sender
{
    UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
    [imagePicker.view setFrame:CGRectMake(0, 80, 320, 350)];
    [imagePicker setSourceType:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
    [imagePicker setDelegate:self];

    [_imagePicker presentViewController:imagePicker animated:YES completion:nil];
}


- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    [picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:nil];
}
@end

Upvotes: 14

Jasper Blues
Jasper Blues

Reputation: 28786

I would set up a capture session as follows:

- (void)setupCaptureSession
{
    NSError* error = nil;

    // Create the session
    _captureSession = [[AVCaptureSession alloc] init];    
    _captureSession.sessionPreset = AVCaptureSessionPresetMedium;
    AVCaptureDevice* device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];

    [_captureSession addInput:input];
    AVCaptureVideoDataOutput* output = [[AVCaptureVideoDataOutput alloc] init];
    [_captureSession addOutput:output];

    // Configure your output.
   dispatch_queue_t queue = dispatch_queue_create("myCameraOutputQueue", NULL);
   //If you want to sebsequently use the data, then implement the delegate.
   [output setSampleBufferDelegate:self queue:queue]; 
}

Having done that, you can create a preview layer as follows:

_previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession];
[_captureSession startRunning];

And then add the preview layer to your view:

_myView.layer addSubLayer:_previewLayer];

Now that you have this set up, I'd add a custom image picker, such as this one: https://github.com/arturgrigor/AGImagePickerController

Upvotes: 1

LombaX
LombaX

Reputation: 17374

The second approach is correct. I don't see your code, but I think that your controller hierarchy is something similar to this:

Main VC ---present---> Camera VC

so, if you call

[self presentViewController:picker animated:YES completion:^{}];

from your Main VC, you are attempting to show another VC from an "hidden" one (covered by the Camera VC).

The key is to take a reference to your camera VC (let's call it cameraVC) and do something similar from Main VC:

 [cameraVC presentViewController:theOtherPicker animated:YES completion:^{}];

doing this, the "present" action is done by the Camera VC (visible) without warnings, and not by the hidden Main VC.

Upvotes: 3

Benjamin Mayo
Benjamin Mayo

Reputation: 6679

You could code your own custom gallery views for selecting photos and then add that to the cameraOverlayView subview hierarchy.

There must be open source projects on GitHub, which show how to make these views, somewhere. Alternatively, I happen to have released a control very similar to what you are looking for if you don't want to start from scratch.

It's quite a simple procedure really — a collection view with a datasource backed by the AssetsLibrary framework.

Upvotes: 1

Related Questions