Swift: Button round corner breaks contraints

2020-02-16 03:22发布

问题:

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.

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)

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

回答1:

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...



回答2:

I looked at your code. The problem is that your roundCorners function is depending on your view's bounds to set your layer's properties and you are calling roundCorners in awakeFromNib at which point your cell has the same bounds as in the NIB file because autolayout has not been calculated yet. You need to move your roundCorners call into layoutSubviews, so it gets called after autolayout is done computing your bounds.

import UIKit

class CustomTableViewCell: UITableViewCell {

    @IBOutlet weak var btnStatus: UIButton!    
    override func layoutSubviews() {
        super.layoutSubviews()
                self.btnStatus.roundCorners(corners: [.bottomRight], radius: 7.0, borderWidth: nil, borderColor: nil)
    }
}

EDIT also add cell.setNeedsLayout() to cellforRowAt to force layoutSubviews to be called before the cell is drawn for the first time.



回答3:

Maybe your button is overlapping since it doesn't have an upper constraint? Try adding a bright border and see if you can see the top part, if not try adding a constraint to the top. Also, instead of giving it a static height, try making it a proportional height to the object above it (maybe the screen size of your device is causing it to overlap)