Saving UIImage with rounded corners and border with Swift

11,251

Solution 1

The basic operations you need to perform are:

  • Clip the drawing area to draw your image into without the corners
  • Draw the image
  • Configure the stroke colour etc
  • Then stroke the path used for clipping

    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
      let borderWidth: CGFloat = 2.0
      let imagePicked = info[UIImagePickerControllerOriginalImage] as UIImage
    
      UIGraphicsBeginImageContextWithOptions(imageViewer.frame.size, false, 0)
    
      let path = UIBezierPath(roundedRect: CGRectInset(imageViewer.bounds, borderWidth / 2, borderWidth / 2), cornerRadius: 10.0)
      let context = UIGraphicsGetCurrentContext()
    
      CGContextSaveGState(context)
      // Clip the drawing area to the path
      path.addClip()
    
      // Draw the image into the context
      imagePicked.drawInRect(imageViewer.bounds)
      CGContextRestoreGState(context)
    
      // Configure the stroke
      UIColor.purpleColor().setStroke()
      path.lineWidth = borderWidth
    
      // Stroke the border
      path.stroke()
    
      roundedImage = UIGraphicsGetImageFromCurrentImageContext();
    
      UIGraphicsEndImageContext();
    
      view.addSubview(UIImageView(image: roundedImage))
    
      picker.dismissViewControllerAnimated(true, completion: nil)
    }
    

In the code above I've inset the path by half of the stroke width because the stroke is drawn along the center of the path, which means that one pixel will end up outside the path.

Solution 2

Paul.s's answer is perfect (⬆️), but since it only captures the size of the UIImage relative to the UIImageView, it can reduce image quality. Assuming you want your image to maintains its aspect ratio within the frame and you want the border to lie directly on its edge, you can do this to maintain the size of the full image for saving:

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {

    let imagePicked = info[UIImagePickerControllerOriginalImage] as UIImage

    let borderWidth: CGFloat = 2.0
    let cornerRadius:CGFloat = 10.0

    // Create a multiplier to scale up the corner radius and border
    // width you decided on relative to the imageViewer frame such
    // that the corner radius and border width can be converted to
    // the UIImage's scale.
    let multiplier:CGFloat = imagePicked.size.height/imageViewer.frame.size.height > imagePicked.size.width/imageViewer.frame.size.width ?
       imagePicked.size.height/imageViewer.frame.size.height :
       imagePicked.size.width/imageViewer.frame.size.width

    let borderWidthMultiplied:CGFloat = borderWidth * multiplier
    let cornerRadiusMultiplied:CGFloat = cornerRadius * multiplier

    UIGraphicsBeginImageContextWithOptions(imagePicked.size, false, 0)

    let path = UIBezierPath(roundedRect: CGRectInset(CGRectMake(0, 0, imagePicked.size.width, imagePicked.size.height),
       borderWidthMultiplied / 2, borderWidthMultiplied / 2), cornerRadius: cornerRadiusMultiplied)

    let context = UIGraphicsGetCurrentContext()

    CGContextSaveGState(context)
    // Clip the drawing area to the path
    path.addClip()

    // Draw the image into the context
    imagePicked.drawInRect(CGRectMake(0, 0, imagePicked.size.width, imagePicked.size.height))
    CGContextRestoreGState(context)

    // Configure the stroke
    UIColor.blackColor().setStroke()
    path.lineWidth = borderWidthMultiplied

    // Stroke the border
    path.stroke()

    imageViewer.image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    picker.dismissViewControllerAnimated(true, completion: nil)


}

Solution 3

Rounded UIImage with border:

extension UIImage {
    
    func roundedWithStroke(width: CGFloat = 3) -> UIImage {
        let imageLayer = CALayer()
        let targetSize = CGSize(width: 29, height: 29)

        imageLayer.frame = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height)
        imageLayer.contents = cgImage
        imageLayer.masksToBounds = true
        imageLayer.cornerRadius = targetSize.width / 2
        imageLayer.borderWidth = width
        imageLayer.borderColor = UIColor.mainAccent.cgColor
        
        UIGraphicsBeginImageContextWithOptions(targetSize, false, scale)
        imageLayer.render(in: UIGraphicsGetCurrentContext()!)
        let roundedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return roundedImage ?? UIImage()
    }
    
}

You can modify extension method to pass parameters that you need.

Solution 4

What you are currently doing is changing the presentation of the image, without changing the image itself. You'll need to create a bitmap context (see UIGraphicsBeginImageContextWithOptions); set up a round rect clipping region (CGContextEOClip); draw your image into the context; and, finally, get a UIImage from the context (UIGraphicsGetImageFromCurrentImageContext)

Share:
11,251
Admin
Author by

Admin

Updated on June 19, 2022

Comments

  • Admin
    Admin almost 2 years

    I am using imagePickerController to pick a picture form the users library. I need to save the picture within the app with rounded corners and a border. When I save the image, it saves the unaltered image. I'm assuming that I am only altering the view not the image itself. Is there a way to save the picture as what can be seen in the view?

    @IBOutlet var imageViewer: UIImageView!
    
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
        var imagePicked = info[UIImagePickerControllerOriginalImage] as UIImage
        imageViewer.layer.cornerRadius = 10.0
        imageViewer.clipsToBounds = true
        imageViewer.layer.frame = CGRectInset(imageViewer.layer.frame, 20, 20)
        imageViewer.layer.borderColor = UIColor.purpleColor().CGColor
        imageViewer.layer.borderWidth = 2.0
        imageViewer.image = imagePicked
    

    Thank you for your help!