Search code examples
iosswiftautolayoutrounded-corners

Swift: Button round corner breaks contraints


I have a tableview with custom cell loaded via xib and in that cell I have status button which bottom right corner should be rounded. The button has constraints Trailing/Leading/Bottom space to superview=0 and height=30.

enter image description here

Without rounding it is working perfectly, as soon as I round one corner for example bottom right the constraints breaks

self.btnStatus.roundCorners(corners: [.bottomRight], radius: 7.0, borderWidth: nil, borderColor: nil)

enter image description here

Some guys here suggesting to call layoutSubviews() but it didn't helped me.

To be more specific I've created simple project where you can have a look into whole project.

Correct Link

ButtonRoundCorner.zip


Solution

  • You can get more reliable results by subclassing your button and placing your "rounding" code by overriding its layoutSubviews() function.

    First, if you want to add a border, you don't want to add multiple "border sublayers" ... so change your UIView extension to this:

    extension UIView {
        func roundCorners(corners: UIRectCorner, radius: CGFloat, borderWidth: CGFloat?, borderColor: UIColor?) {
            let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
            let maskLayer = CAShapeLayer()
            maskLayer.frame = self.bounds
            maskLayer.path = maskPath.cgPath
    
            self.layer.mask = maskLayer
            self.layer.masksToBounds = true
    
            if (borderWidth != nil && borderColor != nil) {
    
                // remove previously added border layer
                for layer in layer.sublayers! {
                    if layer.name == "borderLayer" {
                        layer.removeFromSuperlayer()
                    }
                }
    
                let borderLayer = CAShapeLayer()
    
                borderLayer.frame = self.bounds;
                borderLayer.path  = maskPath.cgPath;
                borderLayer.lineWidth = borderWidth ?? 0;
                borderLayer.strokeColor = borderColor?.cgColor;
                borderLayer.fillColor   = UIColor.clear.cgColor;
                borderLayer.name = "borderLayer"
    
                self.layer.addSublayer(borderLayer);
    
            }
    
        }
    }
    

    Next, add a UIButton subclass:

    class RoundedButton: UIButton {
    
        var corners: UIRectCorner?
        var radius = CGFloat(0.0)
        var borderWidth = CGFloat(0.0)
        var borderColor: UIColor?
    
        override func layoutSubviews() {
    
            super.layoutSubviews()
    
            // don't apply mask if corners is not set, or if radius is Zero
            guard let _corners = corners, radius > 0.0 else {
                return
            }
    
            roundCorners(corners: _corners, radius: radius, borderWidth: borderWidth, borderColor: borderColor)
    
        }
    
    }
    

    This gives you a couple benefits: 1) It will update its mask layer frame when the button frame changes (rotating the device, for example), and 2) you could set these values either from your custom cell class or from cellForRowAt.

    Either way, change your btnStatus class from UIButton to RoundedButton - both in your storyboard and the @IBOutlet connection.

    Then change your CustomTableViewCell to this:

    class CustomTableViewCell: UITableViewCell {
    
        @IBOutlet weak var btnStatus: RoundedButton!
    
        override func awakeFromNib() {
            super.awakeFromNib()
    
            // set up corner maskign
            btnStatus.corners = .bottomRight
            btnStatus.radius = 7.0
    
            // set if desired
    //      btnStatus.borderWidth = 2.0
    //      btnStatus.borderColor = .blue
    
        }
    
        override func setSelected(_ selected: Bool, animated: Bool) {
            super.setSelected(selected, animated: animated)
            // Configure the view for the selected state
        }
    
    }
    

    And finally, your cellForRowAt function becomes:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = self.tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomTableViewCell
        return cell
    }
    

    That should do it...