Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A library to create gradient border button

How to draw a gradient border button?

My Problem

Last week, my partner showed me his design for our application. Everything is great, easily implemented with some custom controls. But wait, something is not in my knowledge.

A button with gradient border. Never try it before. Up to now, I just created gradient background views 2 times in previous projects. Googled and found some good results.

Other solutions

let button = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 100))
let gradient = CAGradientLayer()
gradient.frame =  CGRect(origin: .zero, size: button.frame.size)
gradient.colors = [UIColor.blue.cgColor, UIColor.green.cgColor]
let shape = CAShapeLayer()
shape.lineWidth = 2
shape.path = UIBezierPath(rect: button.bounds).cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
button.layer.addSublayer(gradient)

Awesome, it's perfect.

Rectangles with rounded corners are everywhere! (Steve Jobs)

Tried to make my button rounded corner. And...

Oops. It didn't work as what I need. Did some more search and found another great one from Ian Hirschfeld here https://medium.com/swift-programming/how-to-create-an-angle-gradient-border-in-swift-f4856dde4c90

My solution

It works like a charm, but it's not my favorite solution. I need a Swifty project, not a combination with Objective-C. So don't I try to make something simpler and easier for me and others. Finally, I did it.

The idea

Simple idea, create a button with gradient background, fill it with a solid color view, and the padding to the button bounds is the border width.

Talk is cheap. Show me the code. (Linus Torvalds)

internal func setupView() {    
    let gradientLayer = CAGradientLayer()
    gradientLayer.frame = bounds
    gradientLayer.colors = colors.map({ return $0.cgColor })
    gradientLayer.startPoint = startPoint
    gradientLayer.endPoint = endPoint
    layer.insertSublayer(gradientLayer, at: 0) // * important
    
    let backgroundView = UIView()
    backgroundView.translatesAutoresizingMaskIntoConstraints = false
    insertSubview(backgroundView, at: 1) // * (1)
    backgroundView.backgroundColor = backgroundColor // (2)
	backgroundView.fill(toView: self, space: UIEdgeInsets(space: borderWidth)) // (3)
    createRoundCorner(cornerRadius)
}

(1) - The most important thing here is the order of the gradient layer and background view. The title label will be overlapped and when we use addSublayer or addSubview.

(2) - backgroundColor: the view property from UIKit.

(3) - borderWidth is new property, we will use it every time re-setupView.

Now is time to create the rounded corner.

func createRoundCorner(_ radius: CGFloat) {
    cornerRadius = radius
    super.createRoundCorner(radius)
    backgroundView.createRoundCorner(radius - borderWidth)
}

Every time we set the radius or backgroundColor, setupView called. So that, the gradientLayer and backgroundView are added many times. Keep an instance and remove it every time view setup.

But, no gradient border appears. The border width still zero. We need to give a non-zero value to borderWidth and setupView again.

func createBorder(_ width: CGFloat) {
    borderWidth = width
    setupView()
}

See the result

Completed code

Have a look at my completed code for this library at https://github.com/nguyentruongky/knGradientBorderButton

I use my auto layout library in this project. You can find it here: https://github.com/nguyentruongky/knConstraints

Suggestions or feedback?

Feel free to comment, suggest, create pull request or open issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.