Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save andreacipriani/74ea67db8f17673f1b8b to your computer and use it in GitHub Desktop.
Save andreacipriani/74ea67db8f17673f1b8b to your computer and use it in GitHub Desktop.
iOS: UIImagePickerController editing view circle overlay
/**
Credit: I've started by reading this SO question: http://stackoverflow.com/questions/20794187/uiimagepickercontroller-editing-view-circle-overlay-edited
Trick to add a circle view on image picker editing to facilitate circular cropping
Tested on iPhone4s, iPhone5, iPhone6, iPhone6+, iPad - iOS 7 and iOS 8 - on May 2015
**/
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([navigationController.viewControllers count] == 3 && ([[[[navigationController.viewControllers objectAtIndex:2] class] description] isEqualToString:@"PUUIImageViewController"] || [[[[navigationController.viewControllers objectAtIndex:2] class] description] isEqualToString:@"PLUIImageViewController"]))
{
[self addCircleOverlayToImagePicker:viewController];
}
}
#pragma mark - Circle overlay trick
-(void)addCircleOverlayToImagePicker:(UIViewController*)viewController
{
UIColor *circleColor = [UIColor clearColor];
UIColor *maskColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
UIView *plCropOverlayCropView; //The default crop view, we wan't to hide it and show our circular one
UIView *plCropOverlayBottomBar; //On iPhone is the bar with "cancel" and "choose" button, on Ipad is an Image View with a label saying "Scale and move"
//Subviews hirearchy is different in iPad/iPhone:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
plCropOverlayCropView = [viewController.view.subviews objectAtIndex:1];
plCropOverlayBottomBar = [[[[viewController.view subviews] objectAtIndex:1] subviews] objectAtIndex:1];
//Protect against iOS changes...
if (! [[[plCropOverlayCropView class] description] isEqualToString:@"PLCropOverlay"]){
DDLogWarn(@"Image Picker with circle overlay: PLCropOverlay not found");
return;
}
if (! [[[plCropOverlayBottomBar class] description] isEqualToString:@"UIImageView"]){
DDLogWarn(@"Image Picker with circle overlay: PLCropOverlayBottomBar not found");
return;
}
}
else{
plCropOverlayCropView = [[[viewController.view.subviews objectAtIndex:1] subviews] firstObject];
plCropOverlayBottomBar = [[[[viewController.view subviews] objectAtIndex:1] subviews] objectAtIndex:1];
//Protect against iOS changes...
if (! [[[plCropOverlayCropView class] description] isEqualToString:@"PLCropOverlayCropView"]){
DDLogWarn(@"Image Picker with circle overlay: PLCropOverlayCropView not found");
return;
}
if (! [[[plCropOverlayBottomBar class] description] isEqualToString:@"PLCropOverlayBottomBar"]){
DDLogWarn(@"Image Picker with circle overlay: PLCropOverlayBottomBar not found");
return;
}
}
//It seems that everything is ok, we found the CropOverlayCropView and the CropOverlayBottomBar
plCropOverlayCropView.hidden = YES; //Hide default CropView
CAShapeLayer *circleLayer = [CAShapeLayer layer];
//Center the circleLayer frame:
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0.0f, screenHeight/2 - screenWidth/2, screenWidth, screenWidth)];
circlePath.usesEvenOddFillRule = YES;
circleLayer.path = [circlePath CGPath];
circleLayer.fillColor = circleColor.CGColor;
//Mask layer frame: it begins on y=0 and ends on y = plCropOverlayBottomBar.origin.y
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, screenWidth, screenHeight- plCropOverlayBottomBar.frame.size.height) cornerRadius:0];
[maskPath appendPath:circlePath];
maskPath.usesEvenOddFillRule = YES;
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskPath.CGPath;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.fillColor = maskColor.CGColor;
[viewController.view.layer addSublayer:maskLayer];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
//On iPhone add an hint label on top saying "scale and move" or whatever you want
UILabel *cropLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, screenWidth, 50)];
[cropLabel setText:NSLocalizedString(@"IMAGE_PICKER_CROP_LABEL", nil)];
[cropLabel setTextAlignment:NSTextAlignmentCenter];
[cropLabel setTextColor:[UIColor whiteColor]];
[viewController.view addSubview:cropLabel];
}
else{ //On iPad re-add the overlayBottomBar with the label "scale and move" because we set its parent to hidden (it's a subview of PLCropOverlay)
[viewController.view addSubview:plCropOverlayBottomBar];
}
}
@JaDenis
Copy link

JaDenis commented Mar 17, 2016

it seems UINavigationController delegate method is being executed with pretty big delay on iPhone 4, so that user can see standard cropView for like 0.5 sec before it changes to round mask

also, addCircleOverlayToImagePicker isn't being called if you take a picture with camera

@JaDenis
Copy link

JaDenis commented Mar 17, 2016

dude, change your "didShowViewController" delegate method to "willShowViewController" - no delay after that 👍

still looking into take Photo with Camera issue...

@DeepakSaki
Copy link

great its help me.

@DeepakSaki
Copy link

it is crash in swift 3.0 but successfully run in objective c in xcode 8.2.1 version.

@thuydao
Copy link

thuydao commented Apr 7, 2017

hi @andreacipriani, @JaDenis, thanks for code, i have problem that, i want to change frame of new overlay, but crop frame not change follow new overlay. Please help me for this case. New overlay of me:

UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(25.0f , screenHeight/2 - screenWidth/2, screenWidth-50, screenWidth-50)];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment