3

I have an interesting issue, but I'm not sure how to solve it. I am attempting to extend UIView with an @IBInspectable. However, with this method the corner radius seems to be set from the nib files default side, not the actual size of the view.

So, when in IB I set the "View as" to iPhoneSE and build for iPhoneSE, the view is a circle. However, if I build for iPhone7, the corners are not fully rounded into a circle. Conversely, if I set "View as" to iPhone7 and build for iPhone7, the view is a circle, However, if I build for iPhoneSE the corners are over-rounded.

Pictures and code below:

Extension

extension UIView {

    @IBInspectable var cornerRadius:Double {
        get {
            return Double(layer.cornerRadius)
        }
        set {
            layer.cornerRadius = CGFloat(newValue)
            layer.masksToBounds = newValue > 0
        }
    }

    @IBInspectable var circleView:Bool {
        get {
            return layer.cornerRadius == min(self.frame.width, self.frame.height) / CGFloat(2.0) ? true : false
        }
        set {
            if newValue {
                layer.cornerRadius = min(self.frame.width, self.frame.height) / CGFloat(2.0)
                layer.masksToBounds = true
            }
            else{
                layer.cornerRadius = 0.0
                layer.masksToBounds = false
            }
        }
    }

}

"View as" set as iPhoneSE in IB

Built for iPhoneSE

enter image description here

Build for iPhone 7

enter image description here

"View as" set as iPhone7

Build for iPhone SE

enter image description here

Build for iPhone 7

enter image description here

IB Settings enter image description here enter image description here

steventnorris
  • 5,656
  • 23
  • 93
  • 174

4 Answers4

3

You will only get a full circle if your view is a square.

If you're view is a rectangle and you set the cornerRadius with a value less than half the smallest dimension, you will get the second view and if it's a value greater than that you will get the diamond shaped one.

Check your constraints and you should compute the cornerRadius after the view finishes layout in viewDidLayout so you get the correct size.

Here is a Playground showing this (I've added an animation to show better the issue):

import UIKit
import PlaygroundSupport

extension UIView
{
  func addCornerRadiusAnimation(from: CGFloat, to: CGFloat, duration: CFTimeInterval)
  {
    let animation = CABasicAnimation(keyPath:"cornerRadius")
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    animation.fromValue = from
    animation.toValue = to
    animation.duration = duration
    self.layer.add(animation, forKey: "cornerRadius")
    self.layer.cornerRadius = to
  }
}

let v = UIView(frame: CGRect(x: 0, y: 0, width: 350, height: 450))
PlaygroundPage.current.liveView = v

let squareView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
squareView.backgroundColor = .red
squareView.layer.masksToBounds = true
v.addSubview(squareView)
var cornerRadius = CGFloat(0.5*min(squareView.frame.width,
                                   squareView.frame.height))
squareView.addCornerRadiusAnimation(from: 0, to: cornerRadius, duration: 5)

let rectangleView = UIView(frame: CGRect(x: 10, y: 200, width: 100, height: 200))
rectangleView.backgroundColor = .blue
rectangleView.layer.masksToBounds = true
v.addSubview(rectangleView)
cornerRadius = CGFloat(0.5*min(rectangleView.frame.width,
                                   rectangleView.frame.height))
rectangleView.addCornerRadiusAnimation(from: 0, to: cornerRadius, duration: 5)

let diamondView = UIView(frame: CGRect(x: 200, y: 200, width: 100, height: 200))
diamondView.backgroundColor = .green
diamondView.layer.masksToBounds = true
v.addSubview(diamondView)
cornerRadius = CGFloat(0.5*max(diamondView.frame.width,
                               diamondView.frame.height))
diamondView.addCornerRadiusAnimation(from: 0, to: cornerRadius, duration: 5)

Playground output

FranMowinckel
  • 4,233
  • 1
  • 30
  • 26
  • The view is a square. The issue doesn't seem to be the squareness, it seems to be when the code to round it is run in the lifecycle. It runs prior the constraints fully laying out the view, therefore the roundness is incorrect. – steventnorris Mar 17 '17 at 16:30
  • @steventnorris Because you should compute the `cornerRadius` *after* the view has finished layout (it's in my answer). A quick solution is to subclass the `UIView` and implement that method. – FranMowinckel Mar 17 '17 at 16:34
  • Yeah. That's what I may have to do. I was trying to do this in such a way that it would apply to all UIViews. If I subclass, there's no way to then subclass again. For instance, say I subclass UIView as a MyRoundView. Now, if I want a UILabel I have to decide whether I subclass UILabel and copy that code or subclass MyRoundView and not have the methods and parameters that come with UILabel. – steventnorris Mar 17 '17 at 16:39
  • You could add some `makeRound` method to your extension and call it in your view controller (e.g. in `viewDidLayoutSubviews()`). Or you could loop your view and subviews looking for the `circleView` property. Or you could extend your view controller with a method to `makeRoundCircleSubviews`... – FranMowinckel Mar 17 '17 at 16:46
0

You mentioned Width and Height as 185.5 but you kept width constraint as 350.

  • The issue isn't the constraints. This should work for non-static width/height constraints, and it does not based on where it is run in the lifecycle I'm starting to think. – steventnorris Mar 17 '17 at 16:29
0

If you don't want to move on viewDidLayout you can simply made the trick before setting your corner radius by adding .layoutIfNeeded().

yourCircleView.layoutIfNeeded()
yourCircleView.layer.cornerRadius = yourCircleView.frame.size.width/2
Alex
  • 271
  • 2
  • 4
  • 18
0

If still failing, in Xcode remove all (ALL) restraints and add the following in the Cell Class:

override func awakeFromNib() {
    super.awakeFromNib()        
    userImageView.layoutIfNeeded()
    userImageView.layer.cornerRadius = userImageView.frame.size.width / 2
    userImageView.layer.masksToBounds = true
    userImageView.clipsToBounds = true

}

user3613987
  • 119
  • 1
  • 2