Swift: Create a UIButton with background and border gradients, rounded corners and a shadow
The standard UIButton
is a fairly bland affair. We don’t need to write and inordinate amount of code to spruce it up though. In this tutorial we’ll look at how to add background and border gradients, round its corners and display a drop shadow underneath it.
Note: If you just want the code and aren’t bothered about the explanations, you’ll find a complete subclass at the bottom of this tutorial.
Setup
We’re going to be styling our UIButton
via a subclass so that the design can be trivially reused and updated throughout the app.
Start by creating a new “Cocoa Touch Class” file:
- Class: I’m naming mine
PrimaryButton
. You can name yours whatever you want, just make sure you replacePrimaryButton
with whichever name you chose as you follow along. - Subclass of:
UIButton
- Language: Swift
Next, add a button object to the ViewController
of your choosing. With the button selected, open the Identity Inspector tab in the sidebar and change the button’s class to PrimaryButton
.
That’s all the setup done. We can now get on with the styling.
Styling
We’re going to be working in PrimaryButton.swift
for the remainder of this tutorial so open that up now. You can delete the commented out draw
method.
All of our modifications to the generic UIButton
are going to be done in the layoutSubviews
method, so start by overriding that in the class.
override func layoutSubviews() {
super.layoutSubviews()
// All remaining snippets go here…
}
The border gradient
We’ll start by implementing the border gradient as all our other additions are going to be placed below it. There are two parts to a border gradient: the first is the CAGradientLayer
that, as you might expect, draws the gradient; the second is a mask that defines where the gradient will be visible. If we didn’t create a mask, the gradient layer would take up the whole of the button.
The first portion of the following snippet draws the aforementioned CAGradientLayer
; the second portion defines the mask. It’s the lineWidth
property on borderMask
that determines how thick the stroke around the button will be.
I think frame
is the only property here that isn’t self-explanatory. We’ll be seeing it throughout this post so we might as well explain it here. frame
simply defines the position and size of a layer. Here, borderGradient.frame = self.bounds
translates to “set the position and size values of borderGradient
to be the same as the position and size values of the button”.
// Border:
let borderGradientTop = UIColor(hue:0.72, saturation:0.59, brightness:0.90, alpha:1.00).cgColor
let borderGradientBottom = UIColor(hue:0.72, saturation:0.82, brightness:0.90, alpha:1.00).cgColor
let borderGradient = CAGradientLayer()
borderGradient.frame = self.bounds
borderGradient.colors = [borderGradientTop, borderGradientBottom]
borderGradient.cornerRadius = 4
let borderMask = CAShapeLayer()
borderMask.lineWidth = 1
borderMask.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 4).cgPath
borderMask.fillColor = UIColor.clear.cgColor
borderMask.strokeColor = UIColor.black.cgColor
borderGradient.mask = borderMask
self.layer.insertSublayer(borderGradient, below: self.titleLabel?.layer)
// Next snippet goes here…
The background gradient
The code for the background is almost an exact copy of the first portion of the above snippet, we just need to change the colours.
// Background:
let backgroundGradientTop = UIColor(hue:0.72, saturation:0.58, brightness:0.97, alpha:1.00).cgColor
let backgroundGradientBottom = UIColor(hue:0.72, saturation:0.80, brightness:0.96, alpha:1.00).cgColor
let background = CAGradientLayer()
background.frame = self.bounds
background.colors = [backgroundGradientTop, backgroundGradientBottom]
background.cornerRadius = 4
self.layer.insertSublayer(background, below: borderGradient)
// Next snippet goes here…
The shadow
We can add the shadow via the UIButton
’s layer
property. No need to create another sublayer.
Whilst not particularly intuitive, the width
and height
values of shadowOffset
are in fact what control the shadow’s x
and y
positions.
// Shadow:
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.5)
self.layer.shadowOpacity = 0.2
self.layer.shadowRadius = 1
self.layer.masksToBounds = false
// Next snippet goes here…
The text
If you try running your app now, the button should look great… except for the text. Let’s finish up here by polishing that.
// Text:
self.setTitleColor(UIColor.white, for: .normal)
self.setTitle(self.titleLabel?.text?.uppercased(), for: .normal)
self.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .bold)
Final Subclass
Here’s what your final class should look like:
import UIKit
class PrimaryButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
// Border:
let borderGradientTop = UIColor(hue:0.72, saturation:0.59, brightness:0.90, alpha:1.00).cgColor
let borderGradientBottom = UIColor(hue:0.72, saturation:0.82, brightness:0.90, alpha:1.00).cgColor
let borderGradient = CAGradientLayer()
borderGradient.frame = self.bounds
borderGradient.colors = [borderGradientTop, borderGradientBottom]
borderGradient.cornerRadius = 4
let borderMask = CAShapeLayer()
borderMask.lineWidth = 1
borderMask.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 4).cgPath
borderMask.fillColor = UIColor.clear.cgColor
borderMask.strokeColor = UIColor.black.cgColor
borderGradient.mask = borderMask
self.layer.insertSublayer(borderGradient, below: self.titleLabel?.layer)
// Background:
let backgroundGradientTop = UIColor(hue:0.72, saturation:0.58, brightness:0.97, alpha:1.00).cgColor
let backgroundGradientBottom = UIColor(hue:0.72, saturation:0.80, brightness:0.96, alpha:1.00).cgColor
let background = CAGradientLayer()
background.frame = self.bounds
background.colors = [backgroundGradientTop, backgroundGradientBottom]
background.cornerRadius = 4
self.layer.insertSublayer(background, below: borderGradient)
// Shadow:
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.5)
self.layer.shadowOpacity = 0.2
self.layer.shadowRadius = 1
self.layer.masksToBounds = false
// Text
self.setTitleColor(UIColor.white, for: .normal)
self.setTitle(self.titleLabel?.text?.uppercased(), for: .normal)
self.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .bold)
}
}
I tried to keep things as simple as possible here, but once you’ve got this working I’d recommend creating a variable at the top of the function to manage all of the cornerRadius
properties.
Conclusion
I hope this was of use to you. I’m still a huge fan of “buttons that look like buttons” both from an aesthetics standpoint and, more importantly, from a UX standpoint. If this tutorial helps transform even one UIButton
back into a “button that looks like a button” then I’ll be happy.
Next time we’ll look at how to update the button’s design as its state changes.