UIImagePickerController editing view circle overlay

后端 未结 5 1247
太阳男子
太阳男子 2020-12-13 02:58

I\'ve been able to get pretty far with what I\'ve been wanting to accomplish, and that\'s to replicate iOS\'s built in circular photo cropper for the built in contacts app.

相关标签:
5条回答
  • 2020-12-13 03:23

    Resolved code:

    - (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];
        }
    }
    
    0 讨论(0)
  • 2020-12-13 03:23

    In the code of Jakub Marek, there's an issue with persistent rounded layer if you open a second time the camera.

    to solve it add in your openCamera func:

    editLayer?.removeFromSuperlayer()
    label?.removeFromSuperview()
    

    and replace in private func hideDefaultEditOverlay(view: UIView)

    subview.isHidden = true
    

    by

    subview.removeFromSuperview()
    

    Code :

    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)
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-13 03:28

    For doing this from camera, try using the cameraOverlayView and set your own view. That will work only when picking from camera though and not photo library.

    0 讨论(0)
  • 2020-12-13 03:29

    I've changed the code of @aviatorken89 because it wasn't working on iPhone 6/6+ and iPad. Now it should work with any iPhone's screen size and also on iPad! Tested on iOS 7 and iOS 8.

    All these methods aren't really reliable because they are based on the Image Picker subviews hierarchy, and of course Apple could change it. I've tried to protect the code as far as I could, in order to prevent possibile crashes on future iOS releases.

    I'll try to keep my solution updated on a gist: https://gist.github.com/andreacipriani/74ea67db8f17673f1b8b

    Here is the code:

    - (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];
        }
    } 
    
    0 讨论(0)
  • 2020-12-13 03:38

    Swift 3 version (also with rounded edit layer for pictures taken by camera):

    // 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)
                }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题