UIImagePickerController 编辑视图圆圈覆盖
Posted
技术标签:
【中文标题】UIImagePickerController 编辑视图圆圈覆盖【英文标题】:UIImagePickerController editing view circle overlay 【发布时间】:2013-12-27 03:08:39 【问题描述】:我已经能够完成我一直想要完成的事情,那就是复制 ios 的内置圆形照片裁剪器,用于内置联系人应用程序。但是,我一直在努力让我的CAShapeLayers
正确制作。我正在尝试制作一个直径为 320 px 的透明圆,视图的其余部分填充 0.9 alpha 黑色背景。圆形和矩形在正确的位置,但是,圆形并不像我需要的那样完全透明。
我不知道如何解决这个问题。我感谢您的帮助!代码及截图:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
if ([navigationController.viewControllers count] == 3)
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenHeight = screenRect.size.height;
UIView *plCropOverlay = [[[viewController.view.subviews objectAtIndex:1]subviews] objectAtIndex:0];
plCropOverlay.hidden = YES;
CAShapeLayer *circleLayer = [CAShapeLayer layer];
if (screenHeight == 568)
[circleLayer setPosition:CGPointMake(0.0f,124.0f)];
else
[circleLayer setPosition:CGPointMake(0.0f,80.0f)];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, 0.0f, 320.0f, 320.0f)];
[circleLayer setPath:[path CGPath]];
[circleLayer setFillColor:[[UIColor whiteColor] CGColor]];
circleLayer.opacity = 0.7f;
// Set to 0.7f to show for screenshot purposes; setting to 0.0 would make it invisible and blend in with the below rectangleLayer.
CAShapeLayer *rectangleLayer = [CAShapeLayer layer];
UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:CGRectMake(0.0f, 0.0f, 320.0f, screenHeight - 72)];
[rectangleLayer setPath:[path2 CGPath]];
[rectangleLayer setFillColor:[[UIColor blackColor] CGColor]];
[rectangleLayer setOpacity:0.9f];
[rectangleLayer addSublayer:circleLayer];
[[viewController.view layer] addSublayer:rectangleLayer];
UILabel *moveLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, 320, 50)];
[moveLabel setText:@"Move and Scale"];
[moveLabel setTextAlignment:NSTextAlignmentCenter];
[moveLabel setTextColor:[UIColor whiteColor]];
[viewController.view addSubview:moveLabel];
【问题讨论】:
你试过 UIImagePickerController 的cameraOverlayView
属性吗?
这对我的目的不起作用。请阅读原始问题。
【参考方案1】:
解析代码:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
if ([navigationController.viewControllers count] == 3)
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
UIView *plCropOverlay = [[[viewController.view.subviews objectAtIndex:1]subviews] objectAtIndex:0];
plCropOverlay.hidden = YES;
int position = 0;
if (screenHeight == 568)
position = 124;
else
position = 80;
CAShapeLayer *circleLayer = [CAShapeLayer layer];
UIBezierPath *path2 = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, position, 320.0f, 320.0f)];
[path2 setUsesEvenOddFillRule:YES];
[circleLayer setPath:[path2 CGPath]];
[circleLayer setFillColor:[[UIColor clearColor] CGColor]];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 320, screenHeight-72) cornerRadius:0];
[path appendPath:path2];
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor blackColor].CGColor;
fillLayer.opacity = 0.8;
[viewController.view.layer addSublayer:fillLayer];
UILabel *moveLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, 320, 50)];
[moveLabel setText:@"Move and Scale"];
[moveLabel setTextAlignment:NSTextAlignmentCenter];
[moveLabel setTextColor:[UIColor whiteColor]];
[viewController.view addSubview:moveLabel];
【讨论】:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated 也可用于避免调整大小的延迟。任何方式都很棒。@ “赞……” 这一定是个意外,因为我昨晚才发现这种方法哈哈,虽然很棒而且效果很好,因为从技术上讲,圆的边界是正方形,所以用户永远不知道。 点击“选择”后如何获得圆形图像? (像联系人应用程序使用圆形图像来显示选定区域) 您是如何设法移动视图以进行裁剪和编辑的? 您知道如何为相机执行此操作吗?使用 navigationController 委托,我可以在用相机拍照时访问不同的视图和图层([navigationController.viewControllers count] == 1),但在编辑图像时我不能。你知道怎么做吗?谢谢。【参考方案2】:我更改了@aviatorken89 的代码,因为它不适用于 iPhone 6/6+ 和 iPad。现在它应该适用于任何 iPhone 的屏幕尺寸,也适用于 iPad!在 iOS 7 和 iOS 8 上测试。
所有这些方法都不是真正可靠的,因为它们基于 Image Picker 子视图层次结构,当然 Apple 可以更改它。我已尽我所能保护代码,以防止在未来的 iOS 版本中发生崩溃。
我会尽量根据要点更新我的解决方案:https://gist.github.com/andreacipriani/74ea67db8f17673f1b8b
代码如下:
- (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];
-(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 overlay view, we wan't to hide it and show our circular one
UIView *plCropOverlayBottomBar; //On iPhone this is the bar with "cancel" and "choose" buttons, on Ipad it's 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"])
DLog(@"Warning - Image Picker with circle overlay: PLCropOverlay not found");
return;
if (! [[[plCropOverlayBottomBar class] description] isEqualToString:@"UIImageView"])
DLog(@"Warning - Image Picker with circle overlay: BottomBar 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:@"Scale and move"]; //You should localize it
[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];
【讨论】:
当 UIImagePickerController 显示在弹出窗口中时,此代码似乎不适用于 iPad 2 和 iPad Pro。裁剪区域错误。 您的 gist 链接已损坏 谢谢@Joey。我已经更新了断开的链接(由于我的 Github 昵称发生了变化)!【参考方案3】:Swift 3 版本(相机拍摄的照片也带有圆形编辑层):
// Insert this code to your view controller
private var editLayer: CAShapeLayer!
private var label: UILabel!
override func viewDidLoad()
super.viewDidLoad()
// Rounded edit layer
navigationController?.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(pictureCaptured), name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(pictureRejected), name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object: nil)
@objc private func pictureCaptured()
addRoundedEditLayer(to: ...your UIImagePickerController..., forCamera: true)
@objc private func pictureRejected()
editLayer.removeFromSuperlayer()
label.removeFromSuperview()
deinit
NotificationCenter.default.removeObserver(self)
// MARK: Navigation controller delegate
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
// Image picker in edit mode
if let imageVC = NSClassFromString("PUUIImageViewController")
if viewController.isKind(of: imageVC)
addRoundedEditLayer(to: viewController, forCamera: false)
private func addRoundedEditLayer(to viewController: UIViewController, forCamera: Bool)
hideDefaultEditOverlay(view: viewController.view)
// Circle in edit layer - y position
let bottomBarHeight: CGFloat = 72.0
let position = (forCamera) ? viewController.view.center.y - viewController.view.center.x - bottomBarHeight/2 : viewController.view.center.y - viewController.view.center.x
let viewWidth = viewController.view.frame.width
let viewHeight = viewController.view.frame.height
let emptyShapePath = UIBezierPath(ovalIn: CGRect(x: 0, y: position, width: viewWidth, height: viewWidth))
emptyShapePath.usesEvenOddFillRule = true
let filledShapePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight - bottomBarHeight), cornerRadius: 0)
filledShapePath.append(emptyShapePath)
filledShapePath.usesEvenOddFillRule = true
editLayer = CAShapeLayer()
editLayer.path = filledShapePath.cgPath
editLayer.fillRule = kCAFillRuleEvenOdd
editLayer.fillColor = UIColor.black.cgColor
editLayer.opacity = 0.5
viewController.view.layer.addSublayer(editLayer)
// Move and Scale label
label = UILabel(frame: CGRect(x: 0, y: 10, width: viewWidth, height: 50))
label.text = "Move and Scale"
label.textAlignment = .center
label.textColor = UIColor.white
viewController.view.addSubview(label)
private func hideDefaultEditOverlay(view: UIView)
for subview in view.subviews
if let cropOverlay = NSClassFromString("PLCropOverlayCropView")
if subview.isKind(of: cropOverlay)
subview.isHidden = true
break
else
hideDefaultEditOverlay(view: subview)
【讨论】:
它在浏览图库中的图像时显示一个圆形叠加层,而不是在从图库中选择图像之后。【参考方案4】:要从相机执行此操作,请尝试使用 cameraOverlayView 并设置您自己的视图。这仅在从相机而不是从照片库中选择时才有效。
【讨论】:
如何添加 cameraoverlayView 。也请回答我的问题***.com/questions/32768211/…。谢谢 对于照片库我们可以使用什么?【参考方案5】:在 Jakub Marek 的代码中,如果您再次打开相机,则会出现持久圆形图层的问题。
要解决它,请在您的 openCamera 函数中添加:
editLayer?.removeFromSuperlayer()
label?.removeFromSuperview()
并替换为私有函数 hideDefaultEditOverlay(view: UIView)
subview.isHidden = true
通过
subview.removeFromSuperview()
代码:
func openCamera()
if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerController.SourceType.camera))
imagePicker.sourceType = UIImagePickerController.SourceType.camera
//If you dont want to edit the photo then you can set allowsEditing to false
imagePicker.allowsEditing = true
imagePicker.cameraDevice = .rear
imagePicker.showsCameraControls = true
imagePicker.cameraCaptureMode = .photo
imagePicker.delegate = self
editLayer?.removeFromSuperlayer()
label?.removeFromSuperview()
self.present(imagePicker, animated: true, completion: nil)
else
let alert = UIAlertController(title: NSLocalizedString("Attention",comment:""), message: NSLocalizedString("You don't have any camera",comment:""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK",comment:""), style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
private func hideDefaultEditOverlay(view: UIView)
for subview in view.subviews
if let cropOverlay = NSClassFromString("PLCropOverlayCropView")
if subview.isKind(of: cropOverlay)
subview.removeFromSuperview()
//subview.isHidden = true
break
else
hideDefaultEditOverlay(view: subview)
【讨论】:
以上是关于UIImagePickerController 编辑视图圆圈覆盖的主要内容,如果未能解决你的问题,请参考以下文章
UIImagePickerController、自定义 UIButton 和 AutoLayout
OCMock 模拟 UIImagePickerController