Pretty UIButtons (in Swift)
January 27, 2015

There are lots of options for making pretty UIButtons. The simplest way (for the programmer) is to have someone provide an image of the button and text then just set it as the button’s image. But you can also manipulate different parts of the button in code or interface builder. Here’s an example of what can be done in code.

This tutorial has been updated to Swift 2.0.

Let’s start with a pretty boring (and ugly) button:

To create it, open Xcode and make a new single-view project (or scroll to the bottom and download the example code). Then place the following in the ViewController class:

import UIKit

class ViewController: UIViewController {
  var button: UIButton?

  override func viewDidLoad() {
    super.viewDidLoad()

    // Make button half the view height with a 20 pixel margin
    button = UIButton()
    var buttonFrame = self.view.frame
    let xMargin:CGFloat = 20.0
    let yTopMargin:CGFloat = 40.0
    buttonFrame.origin.x += xMargin
    buttonFrame.origin.y += yTopMargin
    buttonFrame.size.width -= 2.0*xMargin
    buttonFrame.size.height /= 3.0
    button?.frame = buttonFrame

    // Make button gray and add a title
    button?.backgroundColor = UIColor.lightGrayColor()
    button?.setTitle("Oh Canada!", forState: UIControlState.Normal)
    let buttonImage = UIImage(named: "Canada")
    button?.setImage(buttonImage, forState: UIControlState.Normal)
    // Handle button being tapped
    button?.addTarget(self, action: Selector("didTouchButton"), forControlEvents: UIControlEvents.TouchUpInside)
    // Add the button so it's part of the view
    self.view.addSubview(button!)
  }

  func didTouchButton() {

  }

}

You’ll also need to add the “Canada” image to your project. Here’s the image:

Run the project and you’ll see the button. But tapping on it doesn’t do anything. We need to fill out the didTouchButton() function to apply the styling.

First, let’s round the corners and add a border. Change the didTouchButton() function to the following:

func didTouchButton() {
    // When button is tapped, make it pretty
    // round the corners
    button?.layer.cornerRadius = 8.0
    // add a border
    button?.layer.borderColor = UIColor.darkGrayColor().CGColor
    button?.layer.borderWidth = 2.0
}

Run the project again then tap on the button to apply the styling in the didTouchButton() function, you’ll see the button is getting a little less ugly:

Next, let’s make the font bigger and change it to italic. Add these lines to the didTouchButton() function:

// change the text color
button?.setTitleColor(UIColor.darkTextColor(), forState: UIControlState.Normal)
// change the text font & make it bigger
button?.titleLabel?.font = UIFont.italicSystemFontOfSize(28.0)

And run it and tap the button again:

The image and text size & position need to be tweaked. To make it easier to see what we’re doing, let’s change the background colors of the text and imageview:

// set the colors for the subviews of the button so we can see where they are
button?.titleLabel?.backgroundColor = UIColor.yellowColor()
button?.imageView?.backgroundColor = UIColor.orangeColor()

Now we can see the frames for the titleLabel and imageView subviews of the button:

It looks like the image is a bit too big and the text is squished to the side. First let’s make the image a more reasonable size:

// Make the image smaller and move it to the left so we have room for the title
button?.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 110)

By default, imageEdgeInsets is all zeros. Adding the 10 values for top, left and bottom adds a border around the image. The larger 110 value for right leaves more room to the right of the image so we can fit in the text.

That fixes the image. Notice that the orange background of the image doesn’t quite go to the edges of the button anymore, it’s got a 10 pixel margin on the top, left and bottom. But the text is still scrunched up against the right edge of the button. To fix it:

button?.titleEdgeInsets = UIEdgeInsets(top: 180, left: -80, bottom: 0, right: 10)

titleEdgeInsets adjusts the position of the titleLabel. The negative 80 left value stretches it to the right, making it wider by 80 pixels. Setting a larger right value would just move the label without making it wider. The right 10 value moves the title 10 pixels away from the right edge of the button. And to move it down so it’s not overlapping the image, we use the top 120 value to move it down 120 pixels, without making it taller. That’s ok because it’s already tall enough.

That’s not bad, but it would be neat if we could get it to look like an old fashioned suitcase label. So let’s rotate the whole button:
let rotationTransform = CATransform3DMakeRotation(-CGFloat(M_PI)/20, 0, 0, 1.0)
button?.layer.transform = rotationTransform

The first line creates a rotation transform. It’s rotating by -CGFloat(M_PI)/20 around the z axis (the 1.0). The two 0’s tell it not to apply a rotation around the x or y axis. Which gives us a slightly rotated button:

Looks like we’re done so we don’t need the yellow and orange background colors anymore. Just comment out or delete those lines to see the final product:

That’s it. Here’s the example code if you want to play with it: PrettyUIButton on GitHub

For a challenge, see how much of the styling you can reproduce in Interface Builder. You should be able to apply almost all of the styling above without writing any code (the exception is the final rotation). Or browse through Dribbble and try to reproduce the buttons in Interface Builder or code. Here are a few to try:

Sign up by Michael Aleo

The Add Event button in Event Elements by David Kim

Log in button by Brian Potstra

You might need to look up a few more UIButton properties, like tintColor and backgroundColor. To add a shadow:

button?.layer.shadowColor = UIColor.greenColor().CGColor;
button?.layer.shadowOpacity = 0.5
button?.layer.shadowRadius = 12
button?.layer.shadowOffset = CGSizeMake(12.0, 12.0)

You may also need the following if the shadow doesn’t want to show up:

button?.layer.masksToBounds = false