Swift
UIButton
iOS Development
Programming Tutorial
Xcode

Make a UIButton programmatically in Swift

Master System Design with Codemia

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

Introduction

Creating a UIButton programmatically in Swift means instantiating the button in code rather than using Interface Builder. This gives you full control over the button's appearance, layout, and behavior at runtime. The basic steps are to create a UIButton instance, configure its title, style, and target action, set Auto Layout constraints (or a frame), and add it to the view hierarchy. This approach is essential for dynamic UIs where buttons are created based on data or conditions.

Basic UIButton Creation

swift
1import UIKit
2
3class ViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        let button = UIButton(type: .system)
8        button.setTitle("Tap Me", for: .normal)
9        button.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
10        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
11        view.addSubview(button)
12    }
13
14    @objc func buttonTapped() {
15        print("Button was tapped!")
16    }
17}

UIButton(type: .system) creates a standard system button with default styling. The frame sets its position and size. addTarget connects the button's tap event to a method.

UIButton with Auto Layout

swift
1override func viewDidLoad() {
2    super.viewDidLoad()
3
4    let button = UIButton(type: .system)
5    button.setTitle("Submit", for: .normal)
6    button.translatesAutoresizingMaskIntoConstraints = false
7    view.addSubview(button)
8
9    NSLayoutConstraint.activate([
10        button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
11        button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
12        button.widthAnchor.constraint(equalToConstant: 200),
13        button.heightAnchor.constraint(equalToConstant: 50)
14    ])
15}

Setting translatesAutoresizingMaskIntoConstraints = false is required when using Auto Layout programmatically. Without it, the system creates conflicting constraints from the frame.

Customizing Appearance

swift
1let button = UIButton(type: .system)
2button.setTitle("Sign In", for: .normal)
3button.setTitleColor(.white, for: .normal)
4button.backgroundColor = UIColor.systemBlue
5button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
6button.layer.cornerRadius = 10
7button.layer.masksToBounds = true
8
9// Different states
10button.setTitle("Signing In...", for: .disabled)
11button.setTitleColor(.gray, for: .disabled)
12button.setTitle("Pressed", for: .highlighted)

UIButton supports different configurations for different states (.normal, .highlighted, .disabled, .selected). Each state can have its own title, color, and image.

Button with Image

swift
1let button = UIButton(type: .system)
2
3// Icon only
4button.setImage(UIImage(systemName: "heart.fill"), for: .normal)
5button.tintColor = .red
6
7// Icon + text
8let textButton = UIButton(type: .system)
9textButton.setTitle("Like", for: .normal)
10textButton.setImage(UIImage(systemName: "heart.fill"), for: .normal)
11textButton.tintColor = .red
12
13// Adjust spacing between image and title (iOS 15+)
14var config = UIButton.Configuration.filled()
15config.title = "Like"
16config.image = UIImage(systemName: "heart.fill")
17config.imagePadding = 8
18config.baseBackgroundColor = .systemPink
19let modernButton = UIButton(configuration: config)

SF Symbols (UIImage(systemName:)) provide scalable icons. For iOS 15+, UIButton.Configuration is the modern API for complex button layouts.

UIButton.Configuration (iOS 15+)

swift
1// Filled button
2var filled = UIButton.Configuration.filled()
3filled.title = "Continue"
4filled.subtitle = "Step 2 of 5"
5filled.image = UIImage(systemName: "arrow.right")
6filled.imagePlacement = .trailing
7filled.cornerStyle = .large
8let filledButton = UIButton(configuration: filled)
9
10// Tinted button
11var tinted = UIButton.Configuration.tinted()
12tinted.title = "Add to Cart"
13tinted.baseBackgroundColor = .systemGreen
14let tintedButton = UIButton(configuration: tinted)
15
16// Plain button
17var plain = UIButton.Configuration.plain()
18plain.title = "Cancel"
19let plainButton = UIButton(configuration: plain)

UIButton.Configuration replaces much of the manual styling code. It supports subtitles, image placement, activity indicators, and dynamic content updates.

Handling Button Actions

swift
1class ViewController: UIViewController {
2    override func viewDidLoad() {
3        super.viewDidLoad()
4
5        // Target-action pattern
6        let button1 = UIButton(type: .system)
7        button1.setTitle("Target-Action", for: .normal)
8        button1.addTarget(self, action: #selector(handleTap(_:)), for: .touchUpInside)
9
10        // Closure-based (iOS 14+)
11        let button2 = UIButton(type: .system, primaryAction: UIAction { action in
12            print("Closure button tapped")
13        })
14        button2.setTitle("Closure", for: .normal)
15    }
16
17    @objc func handleTap(_ sender: UIButton) {
18        print("Button tapped: \(sender.titleLabel?.text ?? "")")
19    }
20}

iOS 14 introduced UIAction-based button handling, which eliminates the need for @objc methods and target-action boilerplate. For iOS 13 and earlier, use the traditional addTarget approach.

Creating Buttons Dynamically

swift
1func createButtons(titles: [String]) {
2    let stackView = UIStackView()
3    stackView.axis = .vertical
4    stackView.spacing = 12
5    stackView.translatesAutoresizingMaskIntoConstraints = false
6    view.addSubview(stackView)
7
8    NSLayoutConstraint.activate([
9        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
10        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
11    ])
12
13    for (index, title) in titles.enumerated() {
14        let button = UIButton(type: .system)
15        button.setTitle(title, for: .normal)
16        button.tag = index
17        button.addTarget(self, action: #selector(optionTapped(_:)), for: .touchUpInside)
18        stackView.addArrangedSubview(button)
19    }
20}
21
22@objc func optionTapped(_ sender: UIButton) {
23    print("Selected option \(sender.tag)")
24}

Using UIStackView with dynamically created buttons is a common pattern for menus, option lists, and form actions. The tag property identifies which button was tapped.

Common Pitfalls

  • Forgetting translatesAutoresizingMaskIntoConstraints: When using Auto Layout, you must set this to false. Otherwise the button's frame-based position creates conflicting constraints and the layout breaks silently.
  • Not adding to view hierarchy: Calling UIButton(type:) creates the button in memory. You must call view.addSubview(button) to make it visible. A common mistake is configuring the button but forgetting to add it.
  • Using .custom type without configuration: UIButton(type: .custom) creates a button with no default styling — no highlight animation, no tint color. Use .system for standard behavior or explicitly configure all visual states for .custom.
  • Target-action selector typo: If the #selector method name does not match an @objc method, the app crashes at runtime with "unrecognized selector sent to instance." Always verify the method signature.
  • Retaining self in closures: When using UIAction closures that capture self, use [weak self] to avoid retain cycles, especially in view controllers that may be dismissed.

Summary

  • Create buttons with UIButton(type: .system) and configure title, color, and action
  • Use Auto Layout with translatesAutoresizingMaskIntoConstraints = false for responsive layouts
  • Use UIButton.Configuration (iOS 15+) for modern button styling with subtitles and images
  • Use UIAction (iOS 14+) for closure-based tap handling instead of target-action
  • Use UIStackView to layout dynamically created buttons
  • Always set translatesAutoresizingMaskIntoConstraints = false when using programmatic constraints

Course illustration
Course illustration

All Rights Reserved.