iOS
Swift
UIButton
UIImage
UIColor

Creating a UIImage from a UIColor to use as a background image for UIButton

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

Introduction

Using a color image for UIButton backgrounds is still useful even with modern APIs, especially when you need per-state visuals, stretchable assets, or compatibility with older code paths. Instead of shipping many static image files, you can generate tiny images from UIColor at runtime. This article shows a reliable pattern that works well in production.

Generate a Solid Color Image in Swift

The core idea is simple: draw a one pixel image with a color, then reuse it as the button background. UIGraphicsImageRenderer is the modern and safe choice.

swift
1import UIKit
2
3extension UIImage {
4    static func fromColor(_ color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
5        let format = UIGraphicsImageRendererFormat.default()
6        format.scale = UIScreen.main.scale
7
8        let renderer = UIGraphicsImageRenderer(size: size, format: format)
9        return renderer.image { context in
10            color.setFill()
11            context.fill(CGRect(origin: .zero, size: size))
12        }
13    }
14}

This is fully runnable in an iOS app target. The default size of one by one is enough for flat colors and keeps memory use minimal.

Apply State-Specific Backgrounds on UIButton

A key benefit of using images is button state control. You can set a different image for normal, highlighted, and disabled states.

swift
1import UIKit
2
3final class DemoViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        let button = UIButton(type: .system)
8        button.setTitle("Continue", for: .normal)
9        button.setTitleColor(.white, for: .normal)
10
11        button.setBackgroundImage(.fromColor(.systemBlue), for: .normal)
12        button.setBackgroundImage(.fromColor(.systemIndigo), for: .highlighted)
13        button.setBackgroundImage(.fromColor(.systemGray3), for: .disabled)
14
15        button.layer.cornerRadius = 10
16        button.layer.masksToBounds = true
17
18        button.frame = CGRect(x: 40, y: 120, width: 220, height: 48)
19        view.addSubview(button)
20    }
21}

With this setup, state transitions are clear and do not require extra image assets in your bundle.

Handle Stretching and Rounded Designs

If you draw a larger source image with gradients or borders, use cap insets so resizing does not distort corners.

swift
1import UIKit
2
3func roundedBlueBackground() -> UIImage {
4    let size = CGSize(width: 24, height: 24)
5    let renderer = UIGraphicsImageRenderer(size: size)
6
7    let image = renderer.image { _ in
8        let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 8)
9        UIColor.systemBlue.setFill()
10        path.fill()
11    }
12
13    return image.resizableImage(
14        withCapInsets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8),
15        resizingMode: .stretch
16    )
17}

Then set it as a background image and the shape will scale cleanly on different button widths.

Dynamic Colors and Theme Changes

If your app supports light mode and dark mode, prefer dynamic colors like systemBlue. When trait collection changes, regenerate images for accurate appearance.

swift
1override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
2    super.traitCollectionDidChange(previousTraitCollection)
3
4    guard traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) else {
5        return
6    }
7
8    // Re-assign images so dynamic colors are resolved for the new appearance.
9    actionButton.setBackgroundImage(.fromColor(.systemBlue), for: .normal)
10    actionButton.setBackgroundImage(.fromColor(.systemIndigo), for: .highlighted)
11}

This avoids stale images after theme toggles.

iOS 15 and UIButton.Configuration

On modern iOS versions, UIButton.Configuration can handle many style cases without background images. Still, image based backgrounds remain useful when you need exact rendering parity with legacy screens. You can combine both approaches: configuration for text and spacing, generated images for background states.

swift
1import UIKit
2
3func makeConfiguredButton() -> UIButton {
4    var config = UIButton.Configuration.filled()
5    config.title = "Pay Now"
6    config.baseForegroundColor = .white
7
8    let button = UIButton(configuration: config)
9    button.setBackgroundImage(.fromColor(.systemGreen), for: .normal)
10    button.setBackgroundImage(.fromColor(.systemTeal), for: .highlighted)
11    button.layer.cornerRadius = 12
12    button.layer.masksToBounds = true
13    return button
14}

This hybrid pattern eases migration in large codebases where parts of the UI still depend on state images.

Common Pitfalls

One frequent issue is using setBackgroundColor for stateful behavior. UIButton does not provide per-state background color directly, so visuals appear inconsistent during highlight. Another issue is generating large images for flat fills, which wastes memory and can increase rendering cost. A third mistake is forgetting masksToBounds when applying corner radius, causing the image to bleed beyond rounded edges. Finally, with dynamic colors, many teams forget to refresh generated images on appearance changes. That can produce mismatched colors between labels and backgrounds.

Summary

  • Generate a tiny image from UIColor using UIGraphicsImageRenderer.
  • Use setBackgroundImage for per-state button styling.
  • Use resizable images with cap insets for rounded or bordered backgrounds.
  • Rebuild generated images when light or dark appearance changes.
  • Keep source image sizes small for better memory and rendering behavior.

Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Basis Lab
No course covers this one yet

Basis Lab writes one for you from a sentence about what you want to be able to do, then teaches it and checks you understood. Your first course is free.

Build my course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms