potato
potato

Reputation: 4589

UIBezierPath: roundedRect: byRoundingCorners: cornerRadii: acts weird

I'm trying to make 2 corners of a button round. If I select .TopLeft and .BottomLeft like this:

let bezierDisableAdsPath = UIBezierPath(roundedRect: disableAdsButton.bounds, byRoundingCorners: [UIRectCorner.TopLeft , UIRectCorner.BottomLeft] , cornerRadii: CGSizeMake(4.0, 4.0))

        let maskAdsLayer = CAShapeLayer()
        maskAdsLayer.frame = disableAdsButton.bounds
        maskAdsLayer.path = bezierDisableAdsPath.CGPath
        disableAdsButton.layer.mask = maskAdsLayer

than the code works beautifully.

If I chose .TopRight and . BottomRight like this:

let bezierDisableAdsPath = UIBezierPath(roundedRect: disableAdsButton.bounds, byRoundingCorners: [UIRectCorner.TopRight , UIRectCorner.BottomRight] , cornerRadii: CGSizeMake(4.0, 4.0))

        let maskAdsLayer = CAShapeLayer()
        maskAdsLayer.frame = disableAdsButton.bounds
        maskAdsLayer.path = bezierDisableAdsPath.CGPath
        disableAdsButton.layer.mask = maskAdsLayer

than I see no round corners. What is going on here?

I already tried adding maskAdsLayer as a subview and it doesn't work.

Upvotes: 5

Views: 3263

Answers (2)

user1427799
user1427799

Reputation: 879

After finding this answer (thanks for the kickstart), and experimenting with the missing details, here my final understanding:

  • the layer only needs to be created once
  • it must be updated when the view's rectangle changes
  • the best place to capture that is in layoutSubviews()

We can use the ability to have a custom layer shape class for UIView to encapsulate this a bit. So first, we define our own layer class with a CAShapeLayer mask:

class RoundedShapeLayer: CAShapeLayer {
  override init() {
    super.init()
    self.mask = CAShapeLayer()
  }    
  override init(layer: Any) {
    super.init(layer: layer)
    self.mask = CAShapeLayer()
  }    
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.mask = CAShapeLayer()
  }

  func update(for bounds: CGRect) {
    (mask as! CAShapeLayer).path =
        UIBezierPath(roundedRect: bounds,
                     byRoundingCorners: [UIRectCorner.topRight, UIRectCorner.topLeft],
                     cornerRadii: CGSize(width: 10, height: 10)).cgPath

  }
}

Then we can use it in a UIView:

@IBDesignable class RoundedCornerView: UIView {    
  override class var layerClass: AnyClass {
    get {
        return RoundedShapeLayer.self
    }
  }

  override init(frame: CGRect) {
    super.init(frame: frame)
  }

  required init?(coder: NSCoder) {
    super.init(coder: coder)
  }

  override func layoutSubviews() {
    super.layoutSubviews()
    (layer as! RoundedShapeLayer).update(for: bounds)
  }
}

There is more work to make the corners customizable, but that's the spirit.

Upvotes: 0

Rob
Rob

Reputation: 437632

If you are doing this in viewDidLoad, the auto layout constraints may not have been applied by this point, and thus the frame of your button may not be at its final value, and thus disableAdsButton.bounds is probably not what you expect it to be at that point. Thus, the corners that are being rounded to the right may not be visible. You can confirm this by logging the bounds of the button at the point this code runs and then look at it again after the views have been laid out.

You can resolve this by deferring this until viewDidAppear or, better, viewDidLayoutSubviews.

Upvotes: 5

Related Questions