1

I have a custom button class I use to get rounded corners. The class works for some hard coded lengths/widths, but doesn't work for others. I have not been able to get it to work at all with dynamically sized buttons. As you can see by the picture, the button is cut off in various ways. Any idea what I'm doing wrong?

class CustomButton: UIButton{
    
     
       override init(frame: CGRect) {
           super.init(frame: frame)
           setupButton()
       }
       
       
       required init?(coder aDecoder: NSCoder) {
           super.init(coder: aDecoder)
           setupButton()
       }
       
       
       func setupButton() {
           setTitleColor(.white, for: .normal)
           layer.cornerRadius   = frame.size.height/2
           layer.masksToBounds = true
       }
       
}

enter image description here

Thanks for any help.

user3371568
  • 330
  • 3
  • 19
  • 1
    Fectum’s suggest about configuring the corner radius is `layoutSubviews` is a good one, but there must be something else going on because you’re not seeing `cornerRadius` being applied consistently. I’d suggest using the [debug view hierarchy](https://help.apple.com/xcode/mac/current/#/dev4dfdedb7a) feature, to see what the frame of the button is and whether it’s getting clipped by some superview. This sort of behavior is what one can see if you use a misconfiguration clipping mask, above and beyond simple corner radius, or clipping by some super view.. – Rob Sep 06 '20 at 08:00

3 Answers3

1

This happens because you set the corners just after button's creation using initial frame. Button's frame are changing during layout, but its corners are not redrawn. Just add setupButton call to layoutSubviews:

override func layoutSubviews() {
    super.layoutSubviews()
    setupButton()
}

Also I recommend you to use UIBezierPath approach instead of cornerRadius due to possible performance issues.

Fectum
  • 51
  • 3
  • The `layoutSubviews` solution is definitely an improvement. But it wouldn’t explain the OPs problem. And I would not suggest the bezier path approach, either. – Rob Sep 06 '20 at 07:54
  • Tried it, but it doesn't work, Fectum, but thank you for the suggestion. And thanks for your input as well Rob. – user3371568 Sep 06 '20 at 12:09
0

Although you are didn't share the issue code, Seems like you have issues with the layer! CALayer is Not auto-resizing due to frame changes, so it sets by the initial setup and stays the same. So first try to make it more like a view. The following code gives you a customizable gradient view that is reacting to any layout call (even right in the Storyboard!):

@IBDesignable
final class GradientView: UIView {

    @IBInspectable var firstColor: UIColor = .clear { didSet { updateView() } }
    @IBInspectable var secondColor: UIColor = .clear { didSet { updateView() } }

    @IBInspectable var startPoint: CGPoint = CGPoint(x: 0, y: 0) { didSet { updateView() } }
    @IBInspectable var endPoint: CGPoint = CGPoint(x: 1, y: 1) { didSet { updateView() } }

    override class var layerClass: AnyClass { get { CAGradientLayer.self } }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateView()
        layer.frame = bounds
    }

    private func updateView() {
        let layer = self.layer as! CAGradientLayer
        layer.colors = [firstColor, secondColor].map {$0.cgColor}
        layer.startPoint = startPoint
        layer.endPoint = endPoint
    }
}

Preview

Then you can add it to your custom view and frame it to the parent (or any other layout method that you like):

class CustomButton: UIButton{

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

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

    private lazy var gradientView: GradientView = {
        // Setup the gradient here
        let gradient = GradientView(frame: frame)
        gradient.startPoint = .init(x: 0, y: 0)
        gradient.endPoint = .init(x: 1, y: 1)
        gradient.firstColor = .red
        gradient.secondColor = .orange
        return gradient
    }()

    private func addLayerIfNeeded() {
        guard gradientView.superview == nil else { return }
        addSubview(gradientView)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        gradientView.frame =  bounds
    }

    func setupButton() {
        addLayerIfNeeded()
        setTitleColor(.white, for: .normal)
    }
}

Preview

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
  • Thanks for the answer, but this didn't work in stack views or with dynamic resizing. The view and or button kept resetting to the top left of the screen. – user3371568 Sep 11 '20 at 05:31
  • 1
    It works in any layout situation like a charm. Seems like you have issues with layouting your stack. I have changed the layout method to make it more available. – Mojtaba Hosseini Sep 11 '20 at 05:36
  • Thanks Mojtaba. Haven't tried it yet, but awarding the bounty to you for the effort. Cheers. – user3371568 Sep 11 '20 at 06:10
  • Thanks, I hope it helped you to make it **auto resizable** and **Interface builder designable**. – Mojtaba Hosseini Sep 11 '20 at 07:01
0

I was able to fix the issue by applying the solutions in the article to my buttons as opposed to a view. This enabled dynamic resizing with gradients.

Why gradient doesn't cover the whole width of the view

user3371568
  • 330
  • 3
  • 19