Everything about your code is wrong.
photoImageView!.layer.frame = photoImageView!.layer.frame.insetBy(dx: 0, dy: 0)
That line is meaningless. If you inset the frame by zero you are not changing it. So that line does nothing at all.
photoImageView!.layer.masksToBounds = false
photoImageView!.clipsToBounds = true
A layer's masksToBounds
and a view's clipsToBounds
are actually the very same property. So you are setting the same property to false
and then back to true
in the very next line. Thus the first of those two lines does nothing at all.
photoImageView!.layer.cornerRadius = photoImageView!.frame.height/2
That is actually the heart of the matter. The problem is that you are setting the corner radius according to the frame height. But that assumes you know what the frame is. You don't. As you yourself said, this doesn't work if you set autolayout constraints on your view. Why? Because of the order in which things happen:
First, you use the current frame height to set the corner radius.
Then, the constraints kick in and change the frame. So now the corner radius that you set before doesn't "fit" the image view any more.
Moreover, setting the corner radius is a lousy way to clip a view to a circle. The correct way is to mask the view to an actual circle.
So, to sum up: You should use a UIImageView subclass that overrides its own layoutSubviews
to set its own mask to a circle that fits the current size. As the size changes due to constraints, layoutSubviews
will be called and your code will change the mask to fit properly.
(The white circular border can be yet another layer or subview that draws the circle.)
The matter comes up quite often, and I often see the cornerRadius
misused in this same way, so here's an actual implementation:
class CircleImageView : UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
self.layer.sublayers = nil
let radius = min(self.bounds.height, self.bounds.width)/2
let cen = CGPoint(x:self.bounds.width/2, y:self.bounds.height/2)
let r = UIGraphicsImageRenderer(size:self.bounds.size)
var im : UIImage?
var outline : UIImage?
r.image { ctx in
let con = ctx.cgContext
UIColor.black.setFill()
con.addArc(center: cen, radius: radius,
startAngle: 0, endAngle: .pi*2, clockwise: true)
let p = con.path
con.fillPath()
im = ctx.currentImage
con.clear(CGRect(origin:.zero, size:self.bounds.size))
con.addPath(p!)
UIColor.clear.setFill()
UIColor.white.setStroke() // border color, change as desired
con.setLineWidth(4) // border width, change as desired
con.strokePath()
outline = ctx.currentImage
}
// the circle mask
let iv = UIImageView(image:im)
iv.contentMode = .center
iv.frame = self.bounds
self.mask = iv
// the border
let iv2 = UIImageView(image:outline)
iv2.contentMode = .center
iv2.frame = self.bounds
self.addSubview(iv2)
}
}
Result:
Use CircleImageView as your image view and you'll get the right result. I repeat: the important thing is that this will continue to work no matter how the CircleImageView itself is subsequently resized.