8

Basing on the source code below:

    @IBOutlet var myUIImageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.makingRoundedImageProfileWithRoundedBorder()
    }

    private func makingRoundedImageProfileWithRoundedBorder() {    
        // Making a circular image profile.
//        self.myUIImageView.layer.cornerRadius = self.myUIImageView.frame.size.width / 2
        // Making a rounded image profile.
        self.myUIImageView.layer.cornerRadius = 20.0

        self.myUIImageView.clipsToBounds = true

        // Adding a border to the image profile
        self.myUIImageView.layer.borderWidth = 10.0
        self.myUIImageView.layer.borderColor = UIColor.whiteColor().CGColor
    }

Indeed I am able to render a circular or rounded UIImageView, but the problem is that if we add the border, the image leaks a bit. It's way worse with a circular UIImageView, it leaks whenever the border is bent, so LEAKS EVERYWHERE! You can find a screenshot of the result below:

UIImageView rendered

Any way to fix that in Swift? Any sample code which answers to this question will be highly appreciated.

Note: as far as possible the solution has to be compatible with iOS 7 and 8+.

hjavaher
  • 2,589
  • 3
  • 30
  • 52
King-Wizard
  • 15,628
  • 6
  • 82
  • 76

3 Answers3

7

First Solution

Basing on the @Jesper Schläger suggestion

"If I may suggest a quick and dirty solution:

Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0."

Please find the Swift implementation below:

import UIKit

class ViewController: UIViewController {

   @IBOutlet var myUIImageView: UIImageView!

   @IBOutlet var myUIViewBackground: UIView!

   override func viewDidLoad() {
        super.viewDidLoad()

        // Making a circular UIView: cornerRadius = self.myUIImageView.frame.size.width / 2
        // Making a rounded UIView: cornerRadius = 10.0
        self.roundingUIView(self.myUIImageView, cornerRadiusParam: 10)
        self.roundingUIView(self.myUIViewBackground, cornerRadiusParam: 20)
   }

   private func roundingUIView(let aView: UIView!, let cornerRadiusParam: CGFloat!) {
       aView.clipsToBounds = true
       aView.layer.cornerRadius = cornerRadiusParam
   }

}

Second Solution

Would be to set a circle mask over a CALayer.

Please find the Objective-C implementation of this second solution below:

CALayer *maskedLayer = [CALayer layer];
[maskedLayer setFrame:CGRectMake(50, 50, 100, 100)];
[maskedLayer setBackgroundColor:[UIColor blackColor].CGColor];

UIBezierPath *maskingPath = [UIBezierPath bezierPath];
[maskingPath addArcWithCenter:maskedLayer.position
                       radius:40
                   startAngle:0
                     endAngle:360
                    clockwise:TRUE];

CAShapeLayer *maskingLayer = [CAShapeLayer layer];
[maskingLayer setPath:maskingPath.CGPath];

[maskedLayer setMask:maskingLayer];
[self.view.layer addSublayer:maskedLayer];

If you comment out from line UIBezierPath *maskingPath = [UIBezierPath bezierPath]; through [maskedLayer setMask:maskingLayer]; you will see that the layer is a square. However when these lines are not commented the layer is a circle.

Note: I neither tested this second solution nor provided the Swift implementation, so feel free to test it and let me know if it works or not through the comment section below. Also feel free to edit this post adding the Swift implementation of this second solution.

King-Wizard
  • 15,628
  • 6
  • 82
  • 76
  • Note: A masking path is functional but has crispy aliased edges that I haven't been able to correct for yet http://stackoverflow.com/questions/37092333/path-based-mask-with-nice-anti-aliasing – SimplGy May 07 '16 at 18:42
0

I worked on improving the code but it keeps crashing. I'll work on it, but I appear to have got a (rough) version working:

Edit Updated with a slightly nicer version. I don't like the init:coder method but maybe that can factored out/improved

class RoundedImageView: UIView {
    var image: UIImage? {
        didSet {
            if let image = image {
                self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
            }
        }
    }
    var cornerRadius: CGFloat?

    private class func frameForImage(image: UIImage) -> CGRect {
        return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
    }

    override func drawRect(rect: CGRect) {
        if let image = self.image {
            image.drawInRect(rect)

            let cornerRadius = self.cornerRadius ?? rect.size.width/10
            let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
            UIColor.whiteColor().setStroke()
            path.lineWidth = cornerRadius
            path.stroke()
        }
    }
}

let image = UIImage(named: "big-teddy-bear.jpg")

let imageView = RoundedImageView()
imageView.image = image

Let me know if that's the sort of thing you're looking for.

A little explanation:

As I'm sure you've found, the "border" that iOS can apply isn't perfect, and shows the corners for some reason. I found a few other solutions but none seemed to work. The reason this is a subclass of UIView, and not UIImageView, is that drawRect: is not called for subclasses of UIImageView. I am not sure about the performance of this code, but it seems good from my (limited) testing

Original code:

class RoundedImageView: UIView {

    var image: UIImage? {
        didSet {
            if let image = image {
                self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
            }
        }
    }

    private class func frameForImage(image: UIImage) -> CGRect {
        return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
    }

    override func drawRect(rect: CGRect) {
        if let image = self.image {
            self.image?.drawInRect(rect)

            let path = UIBezierPath(roundedRect: rect, cornerRadius: 50)
            UIColor.whiteColor().setStroke()
            path.lineWidth = 10
            path.stroke()
        }
    }
}

let image = UIImage(named: "big-teddy-bear.jpg")

let imageView = RoundedImageView()
imageView.image = image
imageView.layer.cornerRadius = 50
imageView.clipsToBounds = true
Community
  • 1
  • 1
Joseph Duffy
  • 4,566
  • 9
  • 38
  • 68
  • Code crashing + I would like to keep using UIImageView in my storyboard and not using an UIView if possible. So unfortunately it does not fit with my need. – King-Wizard Mar 23 '15 at 23:27
  • I am not sure that it's possible with `UIImageView` but I'd love to see a solution if you get one. Can you post more information about the crash? You may be able to use `IBDesignable` to have it work in interface builder but I'm not sure. Did you downvote for some reason? – Joseph Duffy Mar 23 '15 at 23:32
  • self.image.drawInRect(rect) // fatal error: unexpectedly found nil while unwrapping an Optional value – King-Wizard Mar 23 '15 at 23:42
  • See revised version that doesn't crash when image is `nil` – Joseph Duffy Mar 24 '15 at 20:02
  • Ok, but after having tested I also noticed that the corners of the border are not rounded. You should also fix that or add that. – King-Wizard Mar 24 '15 at 21:34
0

If I may suggest a quick and dirty solution:

Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0.

  • Thank you Jesper Schläger, your solution which fixes the problem and requires less code than manually adding a border to an UIView. Nonetheless in theory we could also fix this problem, adding an UIImage to an UIView and manually drawing a border around the UIImage, I think this is what @Joseph Duffy tries to implement in his response below. – King-Wizard Mar 24 '15 at 21:50