How to make a Image Cropper with Swift 3

I recently had a project where i had to crop an image in specific aspect ratio but I cannot find any complete tutorial so i thought i should make one.

I have divided this tutorial on following steps so that it will be easy for you to understand.

  1. Create a Image Zoom and Scroll.
  2. Calculate the frame of image inside the image view.
  3. Calculate the frame of crop area with respect to the actual image size.
  4. perform cropping

Create a Image Zoom and Scroll

Lets start by creating a ViewController. so, go to your storyboard file and drag a new ViewController from the Object Library on the bottom right corner of XCode. now drag a ScrollView on ViewController and adjust the frames of ScrollView to the edge of ViewController and drag a ImageView under the ScrollView and adjust the frame of ImageView to the edge of ScrollView. To add all the missing constraints, simply go the bottom right corner of the storyboard and click on Add Missing Constraints to All Views.

For now set an image on ImageView from ImageAsset but later in your application you can take image from camera or photo library or any other source and set it to ImageView. also change the content mode to Aspect Fit.

now, create the outlets for ScrollView and ImageView and set delegate, minimum zoom scale and maximum zoom scale of scrollview.

class ImageCropperViewController: UIViewController,UIScrollViewDelegate {
     @IBOutlet var scrollView: UIScrollView!{
          didSet{
               scrollView.delegate = self
               scrollView.minimumZoomScale = 1.0
               scrollView.maximumZoomScale = 10.0
          }
     }
     @IBOutlet var imageView: UIImageView!
}

lastly we need to implement one UIScrollViewDelegate function which will return the view that needs to be scaled. For us, we need to return the ImageView.

func viewForZooming(in scrollView: UIScrollView) -> UIView? {
     return imageView
}

Calculate the frame of image inside the image view

When the content mode of ImageView is Aspect Fit, the image may not fill all the space of ImageView so, the frame of image inside the ImageView may not be equal to its bounds. Create an extension of UIImageView and lets write a function that calculates the frame of image in ImageView.

extension UIImageView{
func imageFrame()->CGRect{
     let imageViewSize = self.frame.size
     guard let imageSize = self.image?.size else{return CGRect.zero}
     let imageRatio = imageSize.width / imageSize.height
     let imageViewRatio = imageViewSize.width / imageViewSize.height
     if imageRatio < imageViewRatio {
        let scaleFactor = imageViewSize.height / imageSize.height
        let width = imageSize.width * scaleFactor
        let topLeftX = (imageViewSize.width - width) * 0.5
        return CGRect(x: topLeftX, y: 0, width: width, height: imageViewSize.height)
     }else{
        let scalFactor = imageViewSize.width / imageSize.width
        let height = imageSize.height * scalFactor
        let topLeftY = (imageViewSize.height - height) * 0.5
        return CGRect(x: 0, y: topLeftY, width: imageViewSize.width, height: height)
     }
   }
}

Calculate the frame of crop area with respect to the actual image size

Now we need to define a cropping area. You can make a whole screen or a specific area a cropping area. To define a cropping area lets drag another View in our ViewController and put in on the same level in hierarchy as ScrollView. add Leading, Trailing, Vertically Center and Aspect Ratio constraints to the View. You can change the constraints as per your requirement. For now, I have set Leading, Trailing and Vertically Center Constant equal to 0 and aspect ratio constant equal to 16:9. also decrease the alpha value of the View so that user can see through it.

if you run the application and check then, you can see that you cannot zoom through the View we have just placed to define our crop area. so, to overcome this problem, lets create a Custom UIView which will passes the touch to the view underneath.

class CropAreaView: UIView {
     override func point(inside point: CGPoint, with event:   UIEvent?) -> Bool {
          return false
     }
}

Set the class of the View in the Storyboard to the custom class we have just created.

After we have successfuly defined the crop area in screen, we have to map that area to actual image.

First, create a outlet of the View.

@IBOutlet var cropAreaView: CropAreaView!

now, define a computed variable that will return the frame of cropping area with respect to actual image.

var cropArea:CGRect{
     get{
          let factor = imageView.image!.size.width/view.frame.width
          let scale = 1/scrollView.zoomScale
          let imageFrame = imageView.imageFrame()
          let x = (scrollView.contentOffset.x + cropAreaView.frame.origin.x - imageFrame.origin.x) * scale * factor
          let y = (scrollView.contentOffset.y + cropAreaView.frame.origin.y - imageFrame.origin.y) * scale * factor
          let width = cropAreaView.frame.size.width * scale * factor
          let height = cropAreaView.frame.size.height * scale * factor
          return CGRect(x: x, y: y, width: width, height: height)
     }
}

Perform Cropping

Actually this will be the easiest task among all above. Drag a Button on ViewController that will trigger cropping. make sure that the button is on the same level in hierarchy as ScrollView and CropView. Also add Bottom Space and Horizontally Center constraints.

finally create action of the button and crop the image.


@IBAction func crop(_ sender: UIButton) {
     let croppedCGImage = imageView.image?.cgImage?.cropping(to: cropArea)
     let croppedImage = UIImage(cgImage: croppedCGImage!)
     imageView.image = croppedImage
}

If you run the application and crop the image, the image will be cropped successfully but there is one issue. The cropped image will be displayed with previous zoom level. so, to fix this we have to reset the zoom level of ScrollView.

scrollView.zoomScale = 1

Thats all. now you have the croppedImage, you can use it as per your requirement. Hope you have enjoyed!! 😋 Thank you!! 🙏

Complete Project: https://github.com/aatish-rajkarnikar/ImageCropper

If you are trying to make Image Cropper like in Instagram, I have a another tutorial about How to make Image Cropper Like Instagram.