13
votes

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
4
Use the cameraOverlayView property of UIImagePickerController to add your custom button.omz
@omz 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.jerik
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.jeremyw
@omz 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...jerik
@jeremyw see comment above.jerik

4 Answers

30
votes

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
2
votes

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
        }
1
votes

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

@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!