raul
raul

Reputation: 101

iOS Custom UIImagePickerController Camera Crop to circle - in preview view

I'm using this code to make a custom camera crop:

UIImagePickerController editing view circle overlay

This works perfectly in camera roll but not taking photos

If I change [navigationController.viewControllers count] == 3 --> [navigationController.viewControllers count] == 1 works for camera too, but not in next view (preview view where you accept to use the photo)

Someone help me??

-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0) {
    NSLog(@"Camara");
    UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init];
    imagePicker.allowsEditing = YES;
    imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
    imagePicker.delegate = self;
    self.isCamera = YES;

    [self presentViewController:imagePicker animated:YES completion:nil];

}else{
    NSLog(@"Carrete");
    UIImagePickerController *imagePickerController = [[UIImagePickerController alloc]init];
    imagePickerController.allowsEditing = YES;
    imagePickerController.delegate = self;
    imagePickerController.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;
    self.isCamera = NO;
    [self presentViewController:imagePickerController animated:YES completion:nil];
}

}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
if (self.isCamera) {
    if ([navigationController.viewControllers count] == 1)
    {
        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];
    }

}else{
    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];
    }

}

}

Upvotes: 3

Views: 6323

Answers (5)

Luke
Luke

Reputation: 1

I found this https://github.com/benedom/SwiftyCrop?tab=readme-ov-file on github. I think it does exactly what you need and its very easy to work with.

Upvotes: 0

Rillieux
Rillieux

Reputation: 707

Similar solution in SwiftUI, Swift 5

I banged my head on this for a long, long time, but finally have a solution that works.

I've not been coding in Swift or SwiftUI for long, and I absolutely welcome comments to improve this code. In some places, I've left in a bit of Debugging code you can uncomment. Wrapping my head around the math involved more trial and error than competent, well-thought out approaches!

Shortcomings in this code: first, it would be nice to open the Impage picker from ContentView() and then show my custom view. I don't know how to do that. Second, if there is already an image in the ContentView(), it would be nice to populate the image in the custom view. However, then the user may expect to be able to get the "original" image and move it and scale it. That would require more than is needed for this answer. Ie, do you want to save the original photo in some url / application folder as well as the cropped version? Or even save a dictionary with the original picture and the CGRect needed to recreate the cropped view? Third. there seems to be a bug if the selected photo is exactly the size of the screen (a screenshot); it can be scaled too low.

In a new SwiftUI lifecycle app, I have the following SwiftUI views:

This is what you'll get:

SwiftUI example of photo move and scale app SwiftUI circlemask screen for photo selection ImagePicker UIRepresentable in SwiftUI Selected Image in SwiftUI view Scaled Image in custom SwiftUI view Saved cropped and scqaled image for contact image

I also use this crucial solution for cropping:

  • ImageManipulation.swift

Finally, some of my code accesses system UIcolors so I use the extension in

  • Colors.swift

ContentView

    import SwiftUI
    
    struct ContentView: View {
        
        @State private var isShowingPhotoSelectionSheet = false

        @State private var finalImage: UIImage?
        @State private var inputImage: UIImage?
        
        var body: some View {
            
            VStack {
                
                if finalImage != nil {
                    Image(uiImage: finalImage!)
                        .resizable()
                        .frame(width: 100, height: 100)
                        .scaledToFill()
                        .aspectRatio(contentMode: .fit)
                        .clipShape(Circle())
                        .shadow(radius: 4)
                } else {
                    Image(systemName: "person.crop.circle.fill")
                        .resizable()
                        .scaledToFill()
                        .frame(width: 100, height: 100)
                        .aspectRatio(contentMode: .fit)
                        .foregroundColor(.systemGray2)
                }
                Button (action: {
                    self.isShowingPhotoSelectionSheet = true
                }, label: {
                    Text("Change photo")
                        .foregroundColor(.systemRed)
                        .font(.footnote)
                })
            }
            .background(Color.systemBackground)
            .statusBar(hidden: isShowingPhotoSelectionSheet)
            .fullScreenCover(isPresented: $isShowingPhotoSelectionSheet, onDismiss: loadImage) {
                ImageMoveAndScaleSheet(croppedImage: $finalImage)
            }
        }
        
        func loadImage() {
            guard let inputImage = inputImage else { return }
            finalImage = inputImage
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }

Clicking / tapping on Change photo brings up the next view:

ImagemoveAndScaleSheet

This is a fullscreen modal which hides the statusbar while open.

    import SwiftUI
    
    struct ImageMoveAndScaleSheet: View {
        
        @Environment(\.presentationMode) var presentationMode
        
        @State private var isShowingImagePicker = false

        ///The croped image is what will will send back to the parent view.
        ///It should be the part of the image in the square defined by the
        ///cutout circle's diamter. See below, the cutout circle has an "inset" value
        ///which can be changed.
        @Binding var croppedImage: UIImage?

        ///The input image is received from the ImagePicker.
        ///We will need to calculate and refer to its aspectr ratio in the functions.
        @State private var inputImage: UIImage?
        @State private var inputW: CGFloat = 750.5556577
        @State private var inputH: CGFloat = 1336.5556577
        
        @State private var theAspectRatio: CGFloat = 0.0

        ///The profileImage is what wee see on this view. When added from the
        ///ImapgePicker, it will be sized to fit the screen,
        ///meaning either its width will match the width of the device's screen,
        ///or its height will match the height of the device screen.
        ///This is not suitable for landscape mode or for iPads.
        @State private var profileImage: Image?
        @State private var profileW: CGFloat = 0.0
        @State private var profileH: CGFloat = 0.0
        
        ///Zoom and Drag ...
        @State private var currentAmount: CGFloat = 0
        @State private var finalAmount: CGFloat = 1
        
        @State private var currentPosition: CGSize = .zero
        @State private var newPosition: CGSize = .zero
        
        ///We track of amount the image is moved for use in functions below.
        @State private var horizontalOffset: CGFloat = 0.0
        @State private var verticalOffset: CGFloat = 0.0
        
        var body: some View {
            
            ZStack {
                ZStack {
                    Color.black.opacity(0.8)
                    if profileImage != nil {
                        profileImage?
                            .resizable()
                            .scaleEffect(finalAmount + currentAmount)
                            .scaledToFill()
                            .aspectRatio(contentMode: .fit)
                            .offset(x: self.currentPosition.width, y: self.currentPosition.height)
                    } else {
                        Image(systemName: "person.crop.circle.fill")
                            .resizable()
                            .scaleEffect(finalAmount + currentAmount)
                            .scaledToFill()
                            .aspectRatio(contentMode: .fit)
                            .foregroundColor(.systemGray2)
                    }
                }
                Rectangle()
                    .fill(Color.black).opacity(0.55)
                    .mask(HoleShapeMask().fill(style: FillStyle(eoFill: true)))
                VStack {
                    Text((profileImage != nil) ? "Move and Scale" : "Select a Photo by tapping the icon below")
                        .foregroundColor(.white)
                        .padding(.top, 50)
                    Spacer()
                    HStack{
                        ZStack {
                            HStack {
                                Button(
                                    action: {presentationMode.wrappedValue.dismiss()},
                                    label: { Text("Cancel") })
                                Spacer()
                                Button(
                                    action: {
                                        self.save()
                                        presentationMode.wrappedValue.dismiss()
                                        
                                    })
                                    { Text("Save") }
                                    .opacity((profileImage != nil) ? 1.0 : 0.2)
                                    .disabled((profileImage != nil) ? false: true)
                                    
                            }
                            .padding(.horizontal)
                            .foregroundColor(.white)
                            Image(systemName: "circle.fill")
                                .font(.custom("system", size: 45))
                                .opacity(0.9)
                                .foregroundColor(.white)
                            Image(systemName: "photo.on.rectangle")
                                .imageScale(.medium)
                                .foregroundColor(.black)
                                .onTapGesture {
                                    isShowingImagePicker = true
                                }
                        }
                        .padding(.bottom, 5)
                    }
                }
                .padding()
            }
            .edgesIgnoringSafeArea(.all)
            
            //MARK: - Gestures
            
            .gesture(
                MagnificationGesture()
                    .onChanged { amount in
                        self.currentAmount = amount - 1
                        //                    repositionImage()
                    }
                    .onEnded { amount in
                        self.finalAmount += self.currentAmount
                        self.currentAmount = 0
                        repositionImage()
                    }
            )
            .simultaneousGesture(
                DragGesture()
                    .onChanged { value in
                        self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)
                    }
                    .onEnded { value in
                        self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)
                        self.newPosition = self.currentPosition
                        repositionImage()
                    }
            )
            .simultaneousGesture(
                TapGesture(count: 2)
                    .onEnded({
                        resetImageOriginAndScale()
                    })
            )
            .sheet(isPresented: $isShowingImagePicker, onDismiss: loadImage) {
                ImagePicker(image: self.$inputImage)
                    .accentColor(Color.systemRed)
            }
        }
        
        //MARK: - functions
        
        private func HoleShapeMask() -> Path {
            let rect = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            let insetRect = CGRect(x: inset, y: inset, width: UIScreen.main.bounds.width - ( inset * 2 ), height: UIScreen.main.bounds.height - ( inset * 2 ))
            var shape = Rectangle().path(in: rect)
            shape.addPath(Circle().path(in: insetRect))
            return shape
        }
        
        ///Called when the ImagePicker is dismissed.
        ///We want to measure the image receoived and determine the aspect ratio.
        
        private func loadImage() {
            guard let inputImage = inputImage else { return }
            let w = inputImage.size.width
            let h = inputImage.size.height
            profileImage = Image(uiImage: inputImage)
            
            inputW = w
            inputH = h
            theAspectRatio = w / h
            
            resetImageOriginAndScale()
        }
        
        ///The profileImage will size to fit the screen.
        ///But we need to know the width and height
        ///to set the related @State variables.
        ///Douobke-tpping the image will also set it
        ///as it was sized originally upon loading.
        private func resetImageOriginAndScale() {
            withAnimation(.easeInOut){
                if theAspectRatio >= screenAspect {
                    profileW = UIScreen.main.bounds.width
                    profileH = profileW / theAspectRatio
                } else {
                    profileH = UIScreen.main.bounds.height
                    profileW = profileH * theAspectRatio
                }
                currentAmount = 0
                finalAmount = 1
                currentPosition = .zero
                newPosition = .zero
            }
        }
        
        
        private func repositionImage() {
            
            //Screen width
            let w = UIScreen.main.bounds.width
            
            if theAspectRatio > screenAspect {
                profileW = UIScreen.main.bounds.width * finalAmount
                profileH = profileW / theAspectRatio
            } else {
                profileH = UIScreen.main.bounds.height * finalAmount
                profileW = profileH * theAspectRatio
            }

            horizontalOffset = (profileW - w ) / 2
            verticalOffset = ( profileH - w ) / 2
            
            
            ///Keep the user from zooming too far in. Adjust as required by the individual project.
            if finalAmount > 4.0 {
                withAnimation{
                    finalAmount = 4.0
                }
            }
            
            ///The following if statements keep the image filling the circle cutout.
            if profileW >= UIScreen.main.bounds.width {
                
                if newPosition.width > horizontalOffset {
                    withAnimation(.easeInOut) {
                        newPosition = CGSize(width: horizontalOffset + inset, height: newPosition.height)
                        currentPosition = CGSize(width: horizontalOffset + inset, height: currentPosition.height)
                    }
                }
                
                if newPosition.width < ( horizontalOffset * -1) {
                    withAnimation(.easeInOut){
                        newPosition = CGSize(width: ( horizontalOffset * -1) - inset, height: newPosition.height)
                        currentPosition = CGSize(width: ( horizontalOffset * -1 - inset), height: currentPosition.height)
                    }
                }
            } else {
                
                withAnimation(.easeInOut) {
                    newPosition = CGSize(width: 0, height: newPosition.height)
                    currentPosition = CGSize(width: 0, height: newPosition.height)
                }
            }
            
            if profileH >= UIScreen.main.bounds.width {
                
                if newPosition.height > verticalOffset {
                    withAnimation(.easeInOut){
                        newPosition = CGSize(width: newPosition.width, height: verticalOffset + inset)
                        currentPosition = CGSize(width: newPosition.width, height: verticalOffset + inset)
                    }
                }
                
                if newPosition.height < ( verticalOffset * -1) {
                    withAnimation(.easeInOut){
                        newPosition = CGSize(width: newPosition.width, height: ( verticalOffset * -1) - inset)
                        currentPosition = CGSize(width: newPosition.width, height: ( verticalOffset * -1) - inset)
                    }
                }
            } else {
                
                withAnimation (.easeInOut){
                    newPosition = CGSize(width: newPosition.width, height: 0)
                    currentPosition = CGSize(width: newPosition.width, height: 0)
                }
            }
            
            if profileW <= UIScreen.main.bounds.width && theAspectRatio > screenAspect {
                resetImageOriginAndScale()
            }
            if profileH <= UIScreen.main.bounds.height && theAspectRatio < screenAspect {
                resetImageOriginAndScale()
            }
        }
        
        private func save() {
            
            let scale = (inputImage?.size.width)! / profileW
            
            let xPos = ( ( ( profileW - UIScreen.main.bounds.width ) / 2 ) + inset + ( currentPosition.width * -1 ) ) * scale
            let yPos = ( ( ( profileH - UIScreen.main.bounds.width ) / 2 ) + inset + ( currentPosition.height * -1 ) ) * scale
            let radius = ( UIScreen.main.bounds.width - inset * 2 ) * scale
            
            croppedImage = imageWithImage(image: inputImage!, croppedTo: CGRect(x: xPos, y: yPos, width: radius, height: radius))
            
            ///Debug maths
            print("Input: w \(inputW) h \(inputH)")
            print("Profile: w \(profileW) h \(profileH)")
            print("X Origin: \( ( ( profileW - UIScreen.main.bounds.width - inset ) / 2 ) + ( currentPosition.width  * -1 ) )")
            print("Y Origin: \( ( ( profileH - UIScreen.main.bounds.width - inset) / 2 ) + ( currentPosition.height  * -1 ) )")
            
            print("Scale: \(scale)")
            print("Profile:\(profileW) + \(profileH)" )
            print("Curent Pos: \(currentPosition.debugDescription)")
            print("Radius: \(radius)")
            print("x:\(xPos), y:\(yPos)")
        }
        
        let inset: CGFloat = 15
        let screenAspect = UIScreen.main.bounds.width / UIScreen.main.bounds.height
    }

Apart from the drag and scale gestures, the main things to look and (and probably clean up!) are the functions.

  • HoleShapeMask() (cannot remember where that code is, but I know I got it on SO.
  • repositionImage() (much headbanging here)
  • save() which uses the funciton in the ImageManipulation.swift file.

ImagePicker

Again, this is simply from Hacking With Swift. (Thanks Paul!) https://twitter.com/twostraws/

    import SwiftUI

    struct ImagePicker: UIViewControllerRepresentable {
        
        @Environment(\.presentationMode) var presentationMode
        @Binding var image: UIImage?
        
        class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
            let parent: ImagePicker
            
            init(_ parent: ImagePicker) {
                self.parent = parent
            }
            
            func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
                if let uiImage = info[.originalImage] as? UIImage {
                    parent.image = uiImage
                }
                parent.presentationMode.wrappedValue.dismiss()
            }
        }

        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
            let picker = UIImagePickerController()
            picker.delegate = context.coordinator
            return picker
        }

        func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {

        }
    }

ImageManipulation.swift

This contains the following code:

    import UIKit

    func imageWithImage(image: UIImage, croppedTo rect: CGRect) -> UIImage {

        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        let drawRect = CGRect(x: -rect.origin.x, y: -rect.origin.y,
                              width: image.size.width, height: image.size.height)

        context?.clip(to: CGRect(x: 0, y: 0,
                                 width: rect.size.width, height: rect.size.height))

        image.draw(in: drawRect)

        let subImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()
        return subImage!
    }

## Colors.swift ##

A handy extension to access system UIColors in SwiftUI:

    import Foundation
    import SwiftUI

    extension Color {

        static var label: Color {
            return Color(UIColor.label)
        }

        static var secondaryLabel: Color {
            return Color(UIColor.secondaryLabel)
        }

        static var tertiaryLabel: Color {
            return Color(UIColor.tertiaryLabel)
        }

        static var quaternaryLabel: Color {
            return Color(UIColor.quaternaryLabel)
        }

        static var systemFill: Color {
            return Color(UIColor.systemFill)
        }

        static var secondarySystemFill: Color {
            return Color(UIColor.secondarySystemFill)
        }

        static var tertiarySystemFill: Color {
            return Color(UIColor.tertiarySystemFill)
        }

        static var quaternarySystemFill: Color {
            return Color(UIColor.quaternarySystemFill)
        }

        static var systemBackground: Color {
               return Color(UIColor.systemBackground)
        }

        static var secondarySystemBackground: Color {
            return Color(UIColor.secondarySystemBackground)
        }

        static var tertiarySystemBackground: Color {
            return Color(UIColor.tertiarySystemBackground)
        }

        static var systemGroupedBackground: Color {
            return Color(UIColor.systemGroupedBackground)
        }

        static var secondarySystemGroupedBackground: Color {
            return Color(UIColor.secondarySystemGroupedBackground)
        }

        static var tertiarySystemGroupedBackground: Color {
            return Color(UIColor.tertiarySystemGroupedBackground)
        }

        static var systemRed: Color {
            return Color(UIColor.systemRed)
        }

        static var systemBlue: Color {
            return Color(UIColor.systemBlue)
        }

        static var systemPink: Color {
            return Color(UIColor.systemPink)
        }

        static var systemTeal: Color {
            return Color(UIColor.systemTeal)
        }

        static var systemGreen: Color {
            return Color(UIColor.systemGreen)
        }

        static var systemIndigo: Color {
            return Color(UIColor.systemIndigo)
        }

        static var systemOrange: Color {
            return Color(UIColor.systemOrange)
        }

        static var systemPurple: Color {
            return Color(UIColor.systemPurple)
        }

        static var systemYellow: Color {
            return Color(UIColor.systemYellow)
        }

        static var systemGray: Color {
            return Color(UIColor.systemGray)
        }

        static var systemGray2: Color {
            return Color(UIColor.systemGray2)
        }

        static var systemGray3: Color {
            return Color(UIColor.systemGray3)
        }

        static var systemGray4: Color {
            return Color(UIColor.systemGray4)
        }

        static var systemGray5: Color {
            return Color(UIColor.systemGray5)
        }

        static var systemGray6: Color {
            return Color(UIColor.systemGray6)
        }
        
    }


Upvotes: 9

Vlad Schegolev
Vlad Schegolev

Reputation: 31

extension ProfileViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {

func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    guard imagePickerController?.sourceType == .camera else {
        return
    }

    guard let view = viewController.view.subviews(deep: true, where: {
        String(describing: type(of:$0)) == "CAMPreviewView"
    }).first else {
        return
    }

    viewController.view.layoutIfNeeded()

    let camPreviewBounds = view.bounds
    let circleRect = CGRect(
        x: camPreviewBounds.minX + (camPreviewBounds.width - 320) * 0.5,
        y: camPreviewBounds.minY + (camPreviewBounds.height - 320) * 0.5,
        width: 320,
        height: 320
    )

    let path = UIBezierPath(roundedRect: camPreviewBounds, cornerRadius: 0)
    path.append(UIBezierPath(ovalIn: circleRect))

    let layer = CAShapeLayer()
    layer.path = path.cgPath
    layer.fillRule = CAShapeLayerFillRule.evenOdd;
    layer.fillColor = UIColor.black.cgColor
    layer.opacity = 0.8;

    view.layer.addSublayer(layer)
}
}

Upvotes: 1

Zalak Patel
Zalak Patel

Reputation: 1965

Here is the solution which might help you to create crop overlay:-

- (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];
    }
}

Upvotes: 0

Luiz Dur&#227;es
Luiz Dur&#227;es

Reputation: 754

Although I believe that my reply might be too late, I ended it up mixing my solution with this one: https://gist.github.com/hamin/e8c6dfe00d9c81375f3e, where:

  1. In order to get overlay working properly on Camera, I was listening to notifications (taken & rejecting picture) due to add or remove circle overlay

  2. Kept the solution mentioned above where I need to loop through UINavigationController and draw circle overlay when it was requested to.

To sum up, please find below my solution written in Swift:

public class CustomPicture: NSObject {
//MARK: - Properties
private var myPickerController: UIImagePickerController?
private var plCropOverlayBottomBar: UIView?
private var customLayer: CAShapeLayer?

//MARK: - Constants
private let screenHeight = UIScreen.mainScreen().bounds.size.height
private let screenWidth = UIScreen.mainScreen().bounds.size.width
private let kCameraNotificationIrisAnimationEnd = "_UIImagePickerControllerUserDidCaptureItem"
private let kCameraNotificationUserRejection = "_UIImagePickerControllerUserDidRejectItem"
private let kPUUIImageViewController = "PUUIImageViewController"
private let kPLUIImageViewController = "PLUIImageViewController"
private let kPLCropOverlayCropView = "PLCropOverlayCropView"
private let kPLCropOverlayBottomBar = "PLCropOverlayBottomBar"

//MARK: - Overrides
deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

//MARK: - Privates
private func camera() {
    listenToCameraNotifications()
    let myPickerController = UIImagePickerController()

    myPickerController.delegate = self
    myPickerController.sourceType = .Camera
    myPickerController.allowsEditing = true

    self.myPickerController = myPickerController

    self.navigationController?.presentViewController(myPickerController, animated: true, completion: nil)
}

private func listenToCameraNotifications() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(cameraNotificationIrisEnd), name: kCameraNotificationIrisAnimationEnd, object: nil)

    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(cameraNotificationRejected), name: kCameraNotificationUserRejection, object: nil)
}

private func photoLibrary() {
    let myPickerController = UIImagePickerController()

    myPickerController.delegate = self
    myPickerController.allowsEditing = true
    myPickerController.sourceType = .PhotoLibrary

    self.myPickerController = myPickerController

    self.navigationController?.presentViewController(myPickerController, animated: true, completion: nil)
}

//MARK: - Selector
/**
 Listen to notification sent after reject button has been touched
 */
func cameraNotificationRejected() {
    customLayer!.removeFromSuperlayer()
    plCropOverlayBottomBar!.removeFromSuperview()
}

/**
 Listen to notification sent after picture has been taken
 */
func cameraNotificationIrisEnd() {
    addCircleOverlay(viewController: self.myPickerController!)
}
}

extension CustomPicture: UINavigationControllerDelegate {
//MARK: - Override
public func navigationController(navigationController: UINavigationController, willShowViewController: UIViewController, animated: Bool) {
    if isImageViewer(navigationController: navigationController) {
        addCircleOverlay(viewController: willShowViewController)
    }
}

//MARK: - Private
private func addCircleOverlay(viewController viewController: UIViewController) {
    hidePLCropOverlay(view: viewController.view)
    setPLCropOverlayBottomBar(view: viewController.view)
    setCustomLayer(viewController: viewController)
}

private func getCirclePath() -> UIBezierPath {
    let circlePath = UIBezierPath(ovalInRect: CGRectMake(0, screenHeight / 2 - screenWidth / 2, screenWidth, screenWidth))
    circlePath.usesEvenOddFillRule = true

    let circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.CGPath
    circleLayer.fillColor = UIColor.clearColor().CGColor

    return circlePath
}

private func getMaskPath(screenWidth screenWidth: CGFloat, screenHeight: CGFloat, circlePath: UIBezierPath) -> UIBezierPath {
    let maskPath = UIBezierPath(roundedRect: CGRectMake(0, 0, screenWidth, screenHeight), cornerRadius: 0)
    maskPath.appendPath(circlePath)
    maskPath.usesEvenOddFillRule = true

    return maskPath
}

private func hidePLCropOverlay(view view: UIView) {
    for myView in view.subviews {
        if myView.isKindOfClass(NSClassFromString(kPLCropOverlayCropView)!) {
            myView.hidden = true
            break
        } else {
            hidePLCropOverlay(view: myView as UIView)
        }
    }
}

private func isImageViewer(navigationController navigationController: UINavigationController) -> Bool {
    if (navigationController.viewControllers.count == 3 &&
        (navigationController.viewControllers[2].dynamicType.description() == kPUUIImageViewController ||
            navigationController.viewControllers[2].dynamicType.description() == kPLUIImageViewController)) {

        return true
    }

    return false
}

private func setPLCropOverlayBottomBar(view view: UIView) {
    for myView in view.subviews {
        if myView.isKindOfClass(NSClassFromString(kPLCropOverlayBottomBar)!) {
            plCropOverlayBottomBar = myView
            break
        }
        else {
            savePLCropOverlayBottomBar(view: myView as UIView)
        }
    }
}

private func setCustomLayer(viewController viewController: UIViewController) {
    let circlePath = getCirclePath()
    let maskPath = getMaskPath(screenWidth: screenWidth, screenHeight: screenHeight, circlePath: circlePath)
    let maskLayer = CAShapeLayer()
    maskLayer.path = maskPath.CGPath
    maskLayer.fillRule = kCAFillRuleEvenOdd
    maskLayer.fillColor = UIColor.blackColor().colorWithAlphaComponent(0.8).CGColor
    customLayer = maskLayer

    viewController.view.layer.addSublayer(customLayer!)
    viewController.view.addSubview(plCropOverlayBottomBar!) // put back overlayBottomBar once we set its parent to hidden (subview of PLCropOverlay)
}
}

Upvotes: 0

Related Questions