UIButton
iOS Development
Swift Programming
UI Design
Code Tutorial

Setting an image for a UIButton in code

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Adding an image to a UIButton is straightforward in UIKit, but the details matter if you want the button to look right in different states and iOS versions. The core APIs are setImage(_:for:) for classic buttons and UIButton.Configuration for newer configuration-based buttons.

The Classic UIKit Approach

For a traditional button, you set the image for a specific control state:

swift
1import UIKit
2
3final class ViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        let button = UIButton(type: .system)
8        button.frame = CGRect(x: 40, y: 120, width: 180, height: 44)
9        button.setTitle("Favorite", for: .normal)
10        button.setImage(UIImage(systemName: "star.fill"), for: .normal)
11        button.tintColor = .systemYellow
12        button.imageView?.contentMode = .scaleAspectFit
13        button.semanticContentAttribute = .forceLeftToRight
14        button.contentHorizontalAlignment = .leading
15
16        view.addSubview(button)
17    }
18}

setImage(_:for:) is state-based, so you can assign different images for .normal, .highlighted, .selected, and other button states.

Different Images for Different States

Buttons often need visual feedback when pressed or selected. You can provide separate images for each state:

swift
button.setImage(UIImage(systemName: "heart"), for: .normal)
button.setImage(UIImage(systemName: "heart.fill"), for: .selected)
button.setImage(UIImage(systemName: "heart.fill"), for: .highlighted)

Then toggle the state in your action:

swift
1button.addTarget(self, action: #selector(toggleFavorite(_:)), for: .touchUpInside)
2
3@objc private func toggleFavorite(_ sender: UIButton) {
4    sender.isSelected.toggle()
5}

This is usually better than swapping images manually inside every touch handler.

Asset Images Versus SF Symbols

If the image comes from your asset catalog, use UIImage(named:):

swift
button.setImage(UIImage(named: "profile_icon"), for: .normal)

If the image is an SF Symbol, use UIImage(systemName:):

swift
button.setImage(UIImage(systemName: "paperplane.fill"), for: .normal)

SF Symbols work especially well with UIButton(type: .system) because tinting and dynamic symbol rendering are built in.

Spacing Between Image and Title

A frequent problem is that the image sits too close to the title. In older button APIs, developers often adjusted edge insets. On newer iOS versions, UIButton.Configuration is cleaner.

swift
1import UIKit
2
3let button = UIButton(type: .system)
4var config = UIButton.Configuration.filled()
5config.title = "Send"
6config.image = UIImage(systemName: "paperplane.fill")
7config.imagePadding = 8
8config.baseBackgroundColor = .systemBlue
9config.baseForegroundColor = .white
10
11button.configuration = config

For iOS 15 and later, this is usually the best way to build a polished image button.

Controlling Placement

If you want the image on the trailing side instead of the leading side, configuration-based buttons make it simple:

swift
1var config = UIButton.Configuration.plain()
2config.title = "Next"
3config.image = UIImage(systemName: "arrow.right")
4config.imagePlacement = .trailing
5config.imagePadding = 6
6
7button.configuration = config

That is much more predictable than older inset juggling.

Tinted and Untinted Images

By default, system buttons may tint template-style images. If you want the original image colors preserved, change the rendering mode:

swift
let image = UIImage(named: "logo")?.withRenderingMode(.alwaysOriginal)
button.setImage(image, for: .normal)

If the image should match your app color, keep it as a template image and set tintColor.

Auto Layout Example

Programmatic buttons are often laid out with constraints rather than frames:

swift
1let button = UIButton(type: .system)
2button.translatesAutoresizingMaskIntoConstraints = false
3button.setImage(UIImage(systemName: "tray.fill"), for: .normal)
4button.setTitle("Archive", for: .normal)
5
6view.addSubview(button)
7
8NSLayoutConstraint.activate([
9    button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
10    button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40)
11])

The image setup stays the same regardless of whether you use frames or Auto Layout.

Common Pitfalls

The biggest pitfall is loading the wrong kind of image. UIImage(named:) looks in your app bundle and asset catalog, while UIImage(systemName:) is only for SF Symbols.

Another pitfall is wondering why the image color changed. That usually happens because the button is rendering the image as a template and applying tintColor.

A third pitfall is mixing old edge-inset tricks with modern UIButton.Configuration without a clear reason. On newer iOS versions, configuration-based layout is usually easier to maintain.

Finally, remember that the button image is state-specific. If the image disappears in a selected or disabled state, check which state you configured.

Summary

  • Use setImage(_:for:) to assign a button image in classic UIKit code
  • Use UIButton.Configuration on newer iOS versions for cleaner image, title, and spacing control
  • Choose UIImage(named:) for asset images and UIImage(systemName:) for SF Symbols
  • Configure images per state when the button should look different while selected or highlighted
  • Pay attention to tinting, rendering mode, and spacing so the button looks intentional

Course illustration
Course illustration

All Rights Reserved.