Using UIEdgeInsets to layout a UIButton

Photo by James Sutton on Unsplash

You can use insets to layout the content of system or custom buttons. With insets you can add or remove space to the button’s title (titleEdgeInsets), image (imageEdgeInsets) and both the title and image together (contentEdgeInsets).

To see how these three properties work, create a button (UIButton) via a xib-file or in code. Give it only a center vertically and horizontally in superview constraint, but not a width constraint for this first example. Give the contentView a blue background color, the imageView a red background color and the titleLabel a yellow background color. On top of that you can also give the border of the button a green color.

button.backgroundColor = .blue
button.imageView?.backgroundColor = .red
button.titleLabel?.backgroundColor = .yellow
button.layer.borderColor = UIColor.green.cgColor
button.layer.borderWidth = 2

The contentEdgeInsets is behaving as you might expect. You can add space around both the imageView and titleLabel by using positive values.

let spacing: CGFloat = 8.0
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: spacing)

The rule when it comes to titleEdgeInsets or imageEdgeInsets is to add equal and opposite offsets to the left and right insets. So if you add for example 8 points to the left title inset, you need to apply -8 points to the right inset. In this way you use the insets only to offset the title or image and not to resize them in any way. If you don’t do this, the calculated layout rect may become too small and the title can be truncated and the spacing will show some weird behaviour.

button.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0)

To flip the title with the image we can use the imageEdgeInsets and titleEdgeInsets to move the imageView behind the titleLabel. First we move the imageView the same amount as the button width to the right and then we subtract the amount of the image width so that the imageView will stay in the content-view. It will look like this:

We set the opposite value of the left inset for the right inset:

let buttonWidth = button.frame.width
let imageWidth = button.imageView!.frame.width
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: buttonWidth-imageWidth, bottom: 0, right: -(buttonWidth-imageWidth))

Then we can move the titleLabel with the same amount as the width of the imageView to the left:

button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageWidth, bottom: 0, right: imageWidth)

If you want to have some spacing between the imageView and titleLabel, you need to add half of the spacing to the titleLabel and half of the spacing to the imageView like this:

let buttonWidth = button.frame.width
let imageWidth = button.imageView!.frame.width
let spacing: CGFloat = 8.0 / 2
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: buttonWidth-imageWidth + spacing, bottom: 0, right: -(buttonWidth-imageWidth) - spacing)
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageWidth - spacing, bottom: 0, right: imageWidth + spacing)

But the titleLabel and imageView now exceeds the content-view. We can compensate for that with the contentEdgeInsets:

button.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: spacing)

But what if we want to have the imageView with a certain margin from the trailing edge and the titleLabel centered?

We can make the button bigger by giving it a bigger spacing, for example 128 points. Then we need to move the imageView the width of the button plus the spacing to the right minus a margin that we want the image to be from the right edge (16 points in our case). We then have to compensate for that same but opposite amount of spacing in the right edgeInset to keep the imageView the same size.

When adding an image the title is pushed to the right with the amount of the image width. In the example above the imageView together with the titleLabel were centered, but now we only need to center the titleLabel. To do so we set the left and right titleEdgeInset half the amount of the imageView’s width. We do it at both sides to keep the titleLabel the same size, otherwise you get a truncated title.

button.imageEdgeInsets = UIEdgeInsets(top: 0, left: buttonWidth + spacing — imageWidth — 16, bottom: 0, right: -spacing)
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageWidth/2, bottom: 0, right: imageWidth/2)
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: spacing)

When you give the button a width constraint, you can set the margin to 0 or leave all the margins out. The width constraint will already broaden the content view.