# Core Graphics: How to Make a Glossy Button

In this tutorial, you’ll learn how to create a customizable, reusable glossy button using only Core Graphics. By Lea Marolt Sonnenschein.

Leave a rating/review
Save for later
Share

30 mins

## Drawing Rounded Rectangles

It’s true that you can easily create square buttons, but button styles come and go faster than the weather changes in Chicago!

In fact, since we originally released this tutorial, square buttons and rounded rectangle buttons have flip-flopped back and forth for the number-one spot in the button pageant, so it’s a good idea to know how to make both versions.

You can also argue that it’s quite easy to create rounded rectangles by simply changing the corner radius of a `UIView`, but where’s the fun in that? There’s so much more gratification, or maybe madness, in doing it the hard way. :]

One way to make rounded rectangles is to draw arcs using the `CGContextAddArc` API. Using that API, you can draw an arc at each corner and draw lines to connect them. But that’s cumbersome and requires a lot of geometry.

Fortunately, there’s an easier way! You don’t have to do as much math and it works well with drawing rounded rectangles. It’s the `CGContextAddArcToPoint` API.

The `CGContextAddArcToPoint` API lets you describe the arc to draw by specifying two tangent lines and a radius. The following diagram from the Quartz2D Programming Guide shows how it works:

When you’re working with a rectangle, you know the tangent lines for each arc you want to draw — they are simply the edges of the rectangle! And you can specify the radius based on how rounded you want the rectangle to be — the larger the arc, the more rounded the corners will be.

The other neat thing about this function is that, if the current point in the path isn’t set to where you tell the arc to begin drawing, it will draw a line from the current point to the beginning of the path. So you can use this as a shortcut to draw a rounded rectangle in just a few calls.

Since you’re going to create a bunch of rounded rectangles in this Core Graphics tutorial, and you want your code to be as reusable as possible, create a separate file for all of your drawing methods.

Go to File ▸ New ▸ File…, and choose iOS ▸ Swift File. Press Next, call it Drawing and click Create.

Now, replace `import Foundation` with the following:

```import UIKit
import CoreGraphics

extension UIView {
func createRoundedRectPath(for rect: CGRect, radius: CGFloat) -> CGMutablePath {
let path = CGMutablePath()

// 1
let midTopPoint = CGPoint(x: rect.midX, y: rect.minY)
path.move(to: midTopPoint)

// 2
let topRightPoint = CGPoint(x: rect.maxX, y: rect.minY)
let bottomRightPoint = CGPoint(x: rect.maxX, y: rect.maxY)
let bottomLeftPoint = CGPoint(x: rect.minX, y: rect.maxY)
let topLeftPoint = CGPoint(x: rect.minX, y: rect.minY)

// 3
tangent2End: bottomRightPoint,

tangent2End: bottomLeftPoint,

tangent2End: topLeftPoint,

tangent2End: topRightPoint,

// 4
path.closeSubpath()

return path
}
}
```

The code above creates a global extension for everything of type `UIView`, so you can use it for more than just `UIButton`s.

The code also instructs `createRoundedRectPath(for:radius:)` to draw the rounded rect in the following order:

Here’s the breakdown of what’s going on in the code:

1. You move to the center of the top line segment.
2. You declare each corner as a local constant.
3. You add each arc to the path:
1. First, you add an arc for the upper right corner. Before drawing an arc, `CGPathAddArcToPoint` will draw a line from the current position in the middle of the rect to the beginning of the arc for you.
2. Similarly, you add an arc for the lower right corner and the connecting line.
3. Then you add an arc for the lower left corner and the connecting line.
4. Last, you add an arc for the upper left corner and the connecting line.
4. Finally, you connect the ending point of the arc with the starting point with `closeSubpath()`.
1. First, you add an arc for the upper right corner. Before drawing an arc, `CGPathAddArcToPoint` will draw a line from the current position in the middle of the rect to the beginning of the arc for you.
2. Similarly, you add an arc for the lower right corner and the connecting line.
3. Then you add an arc for the lower left corner and the connecting line.
4. Last, you add an arc for the upper left corner and the connecting line.

OK, now it’s time to put this method to work! Open CoolButton.swift and replace `draw(_:)` with the following:

```override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}

// 1
let outerColor = UIColor(
hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
let shadowColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)

// 2
let outerMargin: CGFloat = 5.0
let outerRect = rect.insetBy(dx: outerMargin, dy: outerMargin)
// 3
let outerPath = createRoundedRectPath(for: outerRect, radius: 6.0)

// 4
if state != .highlighted {
context.saveGState()
context.setFillColor(outerColor.cgColor)
context.fillPath()
context.restoreGState()
}
}
```

To break this down:

1. You define your two colors.
2. Then you use `insetBy(dx:dy:)` to get a slightly smaller rectangle (5 pixels on each side) where you’ll draw the rounded rect. You’ve made it smaller so that you’ll have space to draw a shadow on the outside.
3. Next, you call the function you just wrote, `createRoundedRectPath(for:radius:)`, to create a path for your rounded rect.
4. Finally, you set the fill color and shadow, add the path to your context and call `fillPath()` to fill it with your current color.
Note: You only want to run the code if your button isn’t currently highlighted; for example, it isn’t being tapped.

Build and run the app; if all works well, you should see the following:

All right, the button’s starting to look pretty good, but you can do even better! How about adding a gradient?

Add the following function to Drawing.swift, to make it universally available for any UIView:

```func drawLinearGradient(
context: CGContext, rect: CGRect, startColor: CGColor, endColor: CGColor) {
// 1
let colorSpace = CGColorSpaceCreateDeviceRGB()

// 2
let colorLocations: [CGFloat] = [0.0, 1.0]

// 3
let colors: CFArray = [startColor, endColor] as CFArray

// 4
colorsSpace: colorSpace, colors: colors, locations: colorLocations)!

// More to come...
}
```

It doesn’t look like much, but there’s a lot going on in this function!

1. The first thing you need is to get a color space that you’ll use to draw the gradient.
2. Note: There’s a lot you can do with color spaces, but 99% of the time you just want a standard device-dependent RGB color space. So simply use the function `CGColorSpaceCreateDeviceRGB()` to get the reference that you need.
3. Next, you set up an array that tracks the location of each color within the range of the gradient. A value of 0 means the start of the gradient, 1 means the end of the gradient. You only have two colors, and you want the first to be at the start and the second to be at the end, so you pass in 0 and 1.
4. Note: You can have three or more colors in a gradient if you want, and you can set where each color begins in the gradient. This can be useful for certain effects.
5. After that, you create an array with the colors that you passed into your function. You use a plain old array here for convenience, but you need to cast it as a `CFArray`, since that’s what the API requires.
6. Then you create your gradient with `CGGradient(colorsSpace:colors:locations:)`, passing in the color space, color array and locations you previously made.
Note: There’s a lot you can do with color spaces, but 99% of the time you just want a standard device-dependent RGB color space. So simply use the function `CGColorSpaceCreateDeviceRGB()` to get the reference that you need.
Note: You can have three or more colors in a gradient if you want, and you can set where each color begins in the gradient. This can be useful for certain effects.

You now have a gradient reference, but it hasn’t actually drawn anything yet. It’s just a pointer to the information you will use when actually drawing it later.

Complete the function by adding the following at the end of `drawLinearGradient(context:rect:startColor:endColor:)`:

```// 5
let startPoint = CGPoint(x: rect.midX, y: rect.minY)
let endPoint = CGPoint(x: rect.midX, y: rect.maxY)

context.saveGState()

// 6
// 7
context.clip()

// 8
gradient, start: startPoint, end: endPoint, options: [])

context.restoreGState()
```
1. The first thing you do is calculate the start and end point where you want to draw the gradient. You just set this as a line from the “top middle” to the “bottom middle” of the rectangle.

The rest of the code helps you draw a gradient into the provided rectangle, the key function being `drawLinearGradient(_:start:end:options:)`.

## Contributors

Author

Author

Tech Editor

Editor

Michael Briscoe

Final Pass Editor