iPhone camera, how to avoid cameraOverlay on preivew view; How to know when entering preview view?
Asked Answered
A

4

14

On the camera workflow, the photo is captured and on the next screen, let's call it choose-screen, you can choose if you want to use this photo or retake it.

How do I know, when the camera enteres the preview view?

My issue is that I have added a button to access the camera roll, which works fine. The obstacle is, when taking a photo and entering the preview view (2. Camera View), the button hides the "use photo" option. So I cannot select it. I want to hide the button when entering the preview view or just avoid the preview view.

enter image description here

Below my code

CamViewScreen.h

#import <UIKit/UIKit.h>
#import "CameraViewController.h"
#import <AssetsLibrary/AssetsLibrary.h>

@interface CamViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) UIImage *lastTakenImage;
- (IBAction)takePhoto:(id)sender;
- (IBAction)selectPhoto:(id)sender;
@end

CamViewScreen.m

#import "CamViewController.h"

@interface CamViewController ()

@end

@implementation CamViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

int isAction = 0; // Photo, 1: CameraRoll, 2: Cancel

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {

        UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:@"Error"
                                                              message:@"Device has no camera"
                                                             delegate:nil
                                                    cancelButtonTitle:@"OK"
                                                    otherButtonTitles: nil];

        [myAlertView show];

    }
    isAction = 0;
    [self cameraRoll];
}

-(void)viewDidAppear:(BOOL)animated {
    // isAction = 0 Photo, 1: CameraRoll, 2: Cancel
    DLog(@"###### isAction> %d", isAction);

    switch (isAction) {
        case 1:
            [self selectPhoto:nil];
            break;
        case 2:
            [self dismissViewControllerAnimated:NO completion:nil];
            break;
        default:
            [self takePhoto:nil];
            break;
    }
}

- (IBAction)takePhoto:(id)sender {
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.allowsEditing = NO;
    picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    picker.cameraOverlayView = [self addCameraRollButton];  // suggestion from omz

    // [self addCameraRollButton:picker.view];

    [self presentViewController:picker animated:YES completion:NULL];
}

-(void)prepareCameraRoll {
    isAction = 1;
    [self dismissViewControllerAnimated:NO completion:nil];

}

- (IBAction)selectPhoto:(id)sender {

    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.allowsEditing = YES;
    picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

    [self presentViewController:picker animated:YES completion:NULL];
}

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    UIImage *chosenImage = info[UIImagePickerControllerEditedImage];
    self.image = chosenImage;

    isAction = 0;
    [picker dismissViewControllerAnimated:YES completion:NULL];
    [self performSegueWithIdentifier:@"toCameraView" sender:info];

}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {

    isAction = 2; // Cancel
    [picker dismissViewControllerAnimated:YES completion:NULL];


}
# pragma mark - for the cameraOverlayView // suggestion from omz
- (UIView *)addCameraRollButton {
    float startY = ([[UIScreen mainScreen] bounds].size.height == 568.0) ? 500.0 : 410.0;

    UIButton *rollButton = [UIButton buttonWithType:UIButtonTypeCustom];
    rollButton.frame = CGRectMake(230.0, startY, 60.0, 60.0);
    rollButton.backgroundColor = [UIColor clearColor];

    [rollButton setImage:self.lastTakenImage forState:UIControlStateNormal];
    rollButton.imageView.contentMode = UIViewContentModeScaleAspectFill;

    [rollButton addTarget:self action:@selector(prepareCameraRoll) forControlEvents:UIControlEventTouchUpInside];

    return rollButton;
}

# pragma mark - CameraRoll function and presentation
- (void)addCameraRollButton:(UIView *)picker {
    float startY = ([[UIScreen mainScreen] bounds].size.height == 568.0) ? 500.0 : 410.0;

    UIButton *rollButton = [UIButton buttonWithType:UIButtonTypeCustom];
    rollButton.frame = CGRectMake(230.0, startY, 60.0, 60.0);
    rollButton.backgroundColor = [UIColor clearColor];

    [rollButton setImage:self.lastTakenImage forState:UIControlStateNormal];
    rollButton.imageView.contentMode = UIViewContentModeScaleAspectFill;

    [rollButton addTarget:self action:@selector(prepareCameraRoll) forControlEvents:UIControlEventTouchUpInside];

    [picker addSubview:rollButton];
}

-(void)cameraRoll {
    ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
    [assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                                 usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
                                     if (nil != group) {
                                         // be sure to filter the group so you only get photos
                                         [group setAssetsFilter:[ALAssetsFilter allPhotos]];

                                         [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
                                             if (asset) {
                                                 ALAssetRepresentation *repr = [asset defaultRepresentation];
                                                 //  UIImage *img = [UIImage imageWithCGImage:[repr fullResolutionImage]];
                                                 UIImage *img = [UIImage imageWithCGImage:[repr fullScreenImage]];
                                                 [self setLastTakenImage:img];
                                                 *stop = YES;
                                             }
                                         }];
                                     }


                                     *stop = NO;
                                 } failureBlock:^(NSError *error) {
                                     NSLog(@"error: %@", error);
                                 }];
}


#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    CameraViewController *cvc = [segue destinationViewController];

    cvc.image = self.image;
    DLog(@"%@, cvcimage", cvc.image);
}
@end
Apophthegm answered 21/5, 2014 at 23:23 Comment(5)
Use the cameraOverlayView property of UIImagePickerController to add your custom button.Semite
@Semite did it, but does not solve the problem. Still the added button is over the "use photo" button. Updated my post, how I did it with the cameraOverlayView property.Apophthegm
Is your CameraViewController (which based on your header file is different from the CamViewController) being called when the photo is taken? You call the method [self performSegueWithIdentifier:@"toCameraView" sender:info] from the didFinishPickingMediaWithInfo: method, which is called when you take the picture. So "toCameraView" is where you need to implement your 'use photo' button.Rebroadcast
@Semite CameraViewController is first called, after the photo was taken; strictly spoken when the button "use photo" is pressed, then the didFinishPickingMediaWithInfo: is called. The camera and function to take a picture is only available on CamViewController. The "use photo" button is the default one, which comes with the UIImagePickerController. I wonder why the overlay is available on two modal view (the camera where you take the picture, and the preview where you use/retake the photo). How do folks have different overlays here...Apophthegm
@Rebroadcast see comment above.Apophthegm
A
31

Found a solution. Was hard to find, but finally got it. The solution is described at UIImagePicker cameraOverlayView appears on Retake screen. Additionally I add my working code for others who have the same problem.

The use of the NSNotificationCenter with @"_UIImagePickerControllerUserDidCaptureItem" and @"_UIImagePickerControllerUserDidRejectItem" is the key!

CamViewController.h

#import <UIKit/UIKit.h>
#import "CameraViewController.h"
#import <AssetsLibrary/AssetsLibrary.h>

@interface CamViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) UIImage *lastTakenImage;
@property (nonatomic, strong) UIImagePickerController *picker;
- (IBAction)takePhoto:(id)sender;
- (IBAction)selectPhoto:(id)sender;
@end

CamViewController.h

#import "CamViewController.h"

@interface CamViewController ()

@end

@implementation CamViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

int isAction = 0; // Photo, 1: CameraRoll, 2: Cancel

- (void)viewDidLoad
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"_UIImagePickerControllerUserDidCaptureItem" object:nil ];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"_UIImagePickerControllerUserDidRejectItem" object:nil ];

    [super viewDidLoad];
    if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {

        UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:@"Error"
                                                              message:@"Device has no camera"
                                                             delegate:nil
                                                    cancelButtonTitle:@"OK"
                                                    otherButtonTitles: nil];

        [myAlertView show];

    }
    isAction = 0;
    [self cameraRoll];
}

-(void)handleNotification:(NSNotification *)message {
    if ([[message name] isEqualToString:@"_UIImagePickerControllerUserDidCaptureItem"]) {
        // Remove overlay, so that it is not available on the preview view;
        self.picker.cameraOverlayView = nil;
    }
    if ([[message name] isEqualToString:@"_UIImagePickerControllerUserDidRejectItem"]) {
        // Retake button pressed on preview. Add overlay, so that is available on the camera again
        self.picker.cameraOverlayView = [self addCameraRollButton];
    }
}

-(void)viewDidAppear:(BOOL)animated {
    // isAction = 0: Photo, 1: CameraRoll, 2: Cancel
    DLog(@"###### isAction> %d", isAction);

    switch (isAction) {
        case 1:
            [self selectPhoto:nil];
            break;
        case 2:
            [self dismissViewControllerAnimated:NO completion:nil];
            break;
        default:
            [self takePhoto:nil];
            break;
    }
}

- (IBAction)takePhoto:(id)sender {
    self.picker = [[UIImagePickerController alloc] init];
    self.picker.delegate = self;
    self.picker.allowsEditing = YES; // if this is NO or missing, the image the image will not be in info[UIImagePickerControllerEditedImage]
    self.picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    self.picker.cameraOverlayView = [self addCameraRollButton];

    [self presentViewController:self.picker animated:YES completion:NULL];
}

-(void)prepareCameraRoll {
    isAction = 1;
    [self dismissViewControllerAnimated:NO completion:nil];

}

- (IBAction)selectPhoto:(id)sender {

    self.picker = [[UIImagePickerController alloc] init];
    self.picker.delegate = self;
    self.picker.allowsEditing = YES;
    self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

    [self presentViewController:self.picker animated:YES completion:NULL];
}

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    self.image = info[UIImagePickerControllerEditedImage]; 

    isAction = 0;
    [picker dismissViewControllerAnimated:YES completion:NULL];
    [self performSegueWithIdentifier:@"toCameraView" sender:info];

}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {

    isAction = 2; // Cancel
    [picker dismissViewControllerAnimated:YES completion:NULL];

}

# pragma mark - CameraRoll function and presentation
- (UIView *)addCameraRollButton {
    float startY = ([[UIScreen mainScreen] bounds].size.height == 568.0) ? 500.0 : 410.0;

    UIButton *rollButton = [UIButton buttonWithType:UIButtonTypeCustom];
    rollButton.frame = CGRectMake(230.0, startY, 60.0, 60.0);
    rollButton.backgroundColor = [UIColor clearColor];

    [rollButton setImage:self.lastTakenImage forState:UIControlStateNormal];
    rollButton.imageView.contentMode = UIViewContentModeScaleAspectFill;

    [rollButton addTarget:self action:@selector(prepareCameraRoll) forControlEvents:UIControlEventTouchUpInside];

    return rollButton;
}

-(void)cameraRoll {
    // have to import assetlibrary framework!!!
    ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
    [assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                                 usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
                                     if (nil != group) {
                                         // be sure to filter the group so you only get photos
                                         [group setAssetsFilter:[ALAssetsFilter allPhotos]];

                                         [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
                                             if (asset) {
                                                 ALAssetRepresentation *repr = [asset defaultRepresentation];
                                                 //  UIImage *img = [UIImage imageWithCGImage:[repr fullResolutionImage]];
                                                 UIImage *img = [UIImage imageWithCGImage:[repr fullScreenImage]];
                                                 [self setLastTakenImage:img];
                                                 *stop = YES;
                                             }
                                         }];
                                     }


                                     *stop = NO;
                                 } failureBlock:^(NSError *error) {
                                     NSLog(@"error: %@", error);
                                 }];
}


#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    CameraViewController *cvc = [segue destinationViewController];

    cvc.image = self.image;
    DLog(@"%@, cvcimage", cvc.image);
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

@end
Apophthegm answered 27/5, 2014 at 21:40 Comment(6)
@matt glad to hear that it could help you :)Apophthegm
@jerik, is using these two notification considered as the private APIs? Just curious, is it documented somewhere?Brookite
@jing i just found the solution in the stackoverflow thread referenzed in the answer. It was really hard to find. If there is a documentation its hidden good ;)Apophthegm
@Apophthegm have you submitted your app to iTunes that uses these notifications? does apple approved your app?Alfons
@Bhushan Yes I submitted it to the apple app store. There were no issues in the approval process regarding these notifications.Apophthegm
Does anyone know how to do this in Swift. I am having trouble converting the ObjC (using my knowledge of it at least) to Swift. @matt, any help?Laughton
R
2

In swift 4 for future references. I'm using isHidden because I had problems to show it back when the user select Retake.

NotificationCenter.default.addObserver(forName: Notification.Name("_UIImagePickerControllerUserDidCaptureItem"),
                                               object: nil,
                                               queue: nil)
        {
            _ in

            overlayView.isHidden = true
            overlayView.isUserInteractionEnabled = false
        }

        NotificationCenter.default.addObserver(forName: Notification.Name("_UIImagePickerControllerUserDidRejectItem"),
                                               object: nil,
                                               queue: nil)
        {
            _ in
            overlayView.isHidden = false
            overlayView.isUserInteractionEnabled = true
        }
Rag answered 1/12, 2017 at 14:52 Comment(1)
This is clean and worked great for Swift 5/XCode 12.3. Should have more upvotes here in 2021. Thank you!!!Mistymisunderstand
S
1

Just posting here for future Swift references, if anyone needs it.

I've been having the same issue, and after a day of research, with a lot of trail and error, I found a way that removes the cameraOverlayView from the image preview.

When you take a photo the "_UIImagePickerControllerUserDidCaptureItem" triggers. Luckily we can utilize this by creating an observer with a closure. Inside the closure we can set the cameraOverlayView to nil, and thereby removing your custom view before entering preview.

This works in Xcode Version 8.1 (8B62), with Swift 3.

I have added the code snippet for you to utilize if needed.

NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object:nil, queue:nil, using: { note in
    self.imagePicker.cameraOverlayView = nil
)}
Sphygmograph answered 7/12, 2016 at 14:49 Comment(2)
You're my hero! 😅 Thanks!Hairless
@Hairless I'm happy that the solution worked for you :)Sphygmograph
R
0

@jerik-I saw you amended your question. You can set the UIImagePickerController property showsCameraControls to no, which will eliminate the preview screen- this hides the default controls as well (and lets you take multiple pictures without dismissing the camera), but since you are using cameraOverlayView, it's immaterial. This only works if the media type is camera, and you will have to implement the takePicture function of the UIImagePickerDelegate protocol to take the picture programatically.

I haven't tried the hack of changing the controls of the preview screen. If you really want to keep the preview screen, you need to add logic to your own takePhoto function that determines if you should call picker.cameraOverlayView = [self addCameraRollButton] (taking a picture) or not (the preview screen), but I'm not sure if you can change the cameraOverlayView for the preview screen dynamically - it seems like something Apple wouldn't want you messing with. Anyway, good luck!

Rebroadcast answered 27/5, 2014 at 1:36 Comment(1)
It's tricky, basically I want to use the default buttons of the camera, but this means to have the preview (as you mentioned). Looks like I have to go the road and create an complete overlay, as well with the default buttons. But I do not know where I can get the default buttons, like cancel, flash, and twist camera.Apophthegm

© 2022 - 2024 — McMap. All rights reserved.