iOS: frame.size.width/2 doesn't produce a circle on every device

You can create an extension to apply the mask and border straight to your image so it will always work disregarding the screen size / scale:

edit/update:

Xcode 11 • Swift 5 or later

extension UIImage {
    var circleMask: UIImage? {
        let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
        let imageView = UIImageView(frame: .init(origin: .init(x: 0, y: 0), size: square))
        imageView.contentMode = .scaleAspectFill
        imageView.image = self
        imageView.layer.cornerRadius = square.width/2
        imageView.layer.borderColor = UIColor.white.cgColor
        imageView.layer.borderWidth = 5
        imageView.layer.masksToBounds = true
        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
        defer { UIGraphicsEndImageContext() }
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        imageView.layer.render(in: context)
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

imgAvatar.image = yourImage.circleMask

Well, Autolayout should be the issue. As it can be seen the height of the imgAvatar on right is greater than the height of the imgAvatar on left.

The size of imgAvatar on right is 190 x 190 and the one on left is 200 x 200, but on the left the corner radius which you are setting is 190/2 i.e. 95px whereas it should be 100px.

Kindly set Height & Width Constraint in your nib file for imgAvatar, if you do not want the imgAvatar to resize by 1:1.

  1. Select imgAvatar in your nib.
  2. Editor > Pin > Height
  3. Select imgAvatar in your nib.
  4. Editor > Pin > Width

OR

Try moving your code to viewDidLayoutSubviews

OR

Subclass your UIImageView and set its corner radius in its awakeFromNib method

Kindly let us know if it worked. Thanks!


If you test in iOS8.3, you should call layoutIfNeed on your UI object before get its frame. Please read iOS8.3 Release notes

For example:

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
// code that sets up the button, but doesn’t yet add it to a window
CGRect titleFrame = button.titleLabel.frame;
// code that relies on the correct value for titleFrame

You now need:

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
// code that sets up the button, but doesn’t yet add it to a window
[button layoutIfNeeded]; // This is also safe pre-iOS 8.3
CGRect titleFrame = button.titleLabel.frame;
// code that relies on the correct value for titleFrame

It's said that for UIButton and sub-classes but you can try it also with your UI object.

When linking against iOS 8.3, any code that relies on layout information (such as the frame) of a UIButton subview when the button is not in the window hierarchy will need to send layoutIfNeeded to the button before retrieving layout information (such as button.titleLabel.frame) to ensure that the layout values are up to date.

Also, as the imgAvatar's cornerRadius was set to 1/2 of imgFrame's size, you will get a circle only if that cornerRadius value = 1/2 of imgAvatar's width (and height).

So, after the call:

imgAvatar.layer.cornerRadius = imgFrame.frame.size.width/2

verify :

  • frame size of imgAvatar and its cornerRadius
  • be sure that image is squared

The problem is that when the circle is applied, the bounds of the view are still considered the default (320x480) and so if you have set constraints related to the screen, the image will be bigger on iPhone 6/6+ for instance.

There are various solution, but if you want a fast one, just put your code:

imgAvatar.layer.cornerRadius = imgFrame.frame.size.width/2

in - viewDidLayoutSubviews in your view controller or in - layoutSubviews in your view, checking before if the cornerRadius is different from the width/2 of the image's frame. Something like this:

if (imgAvatar.layer.cornerRadius != imgFrame.frame.size.width/2) {
    imgAvatar.layer.cornerRadius = imgFrame.frame.size.width/2
}