UIAlertController
custom font
text customization
iOS development
Swift programming

UIAlertController custom font, size, color

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

UIAlertController does not provide a public API for customizing fonts, sizes, or colors of its title, message, or action buttons. The standard approach uses NSAttributedString set via Key-Value Coding (setValue(_:forKey:)) on the attributedTitle and attributedMessage private keys. For action button colors, use the UIView.appearance() API or set the action's titleTextColor via KVC. These techniques work but rely on private API that Apple could change or reject during App Store review. For full customization, build a custom alert view instead.

Custom Title and Message with NSAttributedString

swift
1let alert = UIAlertController(
2    title: "Warning",
3    message: "This action cannot be undone.",
4    preferredStyle: .alert
5)
6
7// Custom title β€” bold, red, larger font
8let titleAttributes: [NSAttributedString.Key: Any] = [
9    .font: UIFont.boldSystemFont(ofSize: 20),
10    .foregroundColor: UIColor.red
11]
12let attributedTitle = NSAttributedString(
13    string: "Warning",
14    attributes: titleAttributes
15)
16alert.setValue(attributedTitle, forKey: "attributedTitle")
17
18// Custom message β€” smaller, gray
19let messageAttributes: [NSAttributedString.Key: Any] = [
20    .font: UIFont.systemFont(ofSize: 14),
21    .foregroundColor: UIColor.darkGray
22]
23let attributedMessage = NSAttributedString(
24    string: "This action cannot be undone. All data will be permanently deleted.",
25    attributes: messageAttributes
26)
27alert.setValue(attributedMessage, forKey: "attributedMessage")
28
29alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
30alert.addAction(UIAlertAction(title: "Delete", style: .destructive))
31
32present(alert, animated: true)

The attributedTitle and attributedMessage keys are private but widely used. They override the plain title and message properties.

Custom Action Button Colors

swift
1let alert = UIAlertController(
2    title: "Choose",
3    message: "Select an option",
4    preferredStyle: .actionSheet
5)
6
7// Regular action with custom color
8let saveAction = UIAlertAction(title: "Save", style: .default) { _ in
9    print("Saved")
10}
11saveAction.setValue(UIColor.systemGreen, forKey: "titleTextColor")
12
13// Custom "warning" action
14let warningAction = UIAlertAction(title: "Reset", style: .default) { _ in
15    print("Reset")
16}
17warningAction.setValue(UIColor.systemOrange, forKey: "titleTextColor")
18
19// Destructive style already uses red
20let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in
21    print("Deleted")
22}
23
24let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
25
26alert.addAction(saveAction)
27alert.addAction(warningAction)
28alert.addAction(deleteAction)
29alert.addAction(cancelAction)
30
31present(alert, animated: true)

Adding an Image to an Action

swift
1let action = UIAlertAction(title: "Share", style: .default) { _ in
2    print("Shared")
3}
4
5// Set image via KVC (private API)
6let image = UIImage(systemName: "square.and.arrow.up")
7action.setValue(image, forKey: "image")
8
9// Left-align the action text (default is center)
10action.setValue(CATextLayerAlignmentMode.left, forKey: "titleTextAlignment")
11
12alert.addAction(action)

Using UIAppearance for Global Tint

swift
1// Change the tint color of all alert actions globally
2// In AppDelegate or SceneDelegate:
3UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .systemPurple
4
5// This affects all non-destructive, non-cancel actions
6// Destructive actions remain red
7// Cancel actions follow the tint color
swift
1// Or set tint on the specific alert's view
2let alert = UIAlertController(title: "Custom Tint", message: nil, preferredStyle: .alert)
3alert.addAction(UIAlertAction(title: "OK", style: .default))
4alert.view.tintColor = .systemPurple
5present(alert, animated: true)
swift
1// For full control, build a custom alert
2class CustomAlertViewController: UIViewController {
3    private let containerView = UIView()
4    private let titleLabel = UILabel()
5    private let messageLabel = UILabel()
6    private let actionButton = UIButton(type: .system)
7
8    var alertTitle: String = ""
9    var alertMessage: String = ""
10    var buttonTitle: String = "OK"
11    var onDismiss: (() -> Void)?
12
13    override func viewDidLoad() {
14        super.viewDidLoad()
15
16        view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
17
18        containerView.backgroundColor = .systemBackground
19        containerView.layer.cornerRadius = 14
20        containerView.translatesAutoresizingMaskIntoConstraints = false
21        view.addSubview(containerView)
22
23        titleLabel.text = alertTitle
24        titleLabel.font = .custom("Avenir-Heavy", size: 18)  // Any font
25        titleLabel.textColor = .label
26        titleLabel.textAlignment = .center
27        titleLabel.translatesAutoresizingMaskIntoConstraints = false
28        containerView.addSubview(titleLabel)
29
30        messageLabel.text = alertMessage
31        messageLabel.font = .systemFont(ofSize: 14)
32        messageLabel.textColor = .secondaryLabel
33        messageLabel.numberOfLines = 0
34        messageLabel.textAlignment = .center
35        messageLabel.translatesAutoresizingMaskIntoConstraints = false
36        containerView.addSubview(messageLabel)
37
38        actionButton.setTitle(buttonTitle, for: .normal)
39        actionButton.titleLabel?.font = .boldSystemFont(ofSize: 16)
40        actionButton.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)
41        actionButton.translatesAutoresizingMaskIntoConstraints = false
42        containerView.addSubview(actionButton)
43
44        NSLayoutConstraint.activate([
45            containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
46            containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
47            containerView.widthAnchor.constraint(equalToConstant: 270),
48
49            titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20),
50            titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
51            titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
52
53            messageLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
54            messageLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
55            messageLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
56
57            actionButton.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
58            actionButton.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
59            actionButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16),
60        ])
61    }
62
63    @objc private func dismissAlert() {
64        dismiss(animated: true) { self.onDismiss?() }
65    }
66}
67
68// Usage
69let custom = CustomAlertViewController()
70custom.alertTitle = "Custom Alert"
71custom.alertMessage = "Full font, size, and color control."
72custom.modalPresentationStyle = .overFullScreen
73custom.modalTransitionStyle = .crossDissolve
74present(custom, animated: true)

Common Pitfalls

  • App Store rejection for private API usage: setValue(_:forKey: "attributedTitle") uses private keys. While many apps ship with this technique, Apple may reject apps during review. For production apps targeting strict review compliance, build a custom alert view.
  • KVC keys changing across iOS versions: Apple can rename or remove private keys in any iOS update without notice. attributedTitle and titleTextColor have been stable since iOS 8, but future iOS versions could break them. Test on every new iOS beta.
  • Setting attributed text without setting the plain text first: Pass the same text to both the UIAlertController initializer and the NSAttributedString. If title is nil but attributedTitle is set, some iOS versions show no title at all.
  • Forgetting dark mode support in custom colors: Hardcoded colors like UIColor.black or UIColor.white look wrong in the opposite appearance mode. Use semantic colors (UIColor.label, UIColor.secondaryLabel) or dynamic colors created with UIColor { trait in ... }.
  • Tint color not applying to destructive actions: alert.view.tintColor affects .default and .cancel actions but not .destructive actions, which always use the system red. Use setValue(_:forKey: "titleTextColor") on the specific destructive action to override its color.

Summary

  • Use setValue(attributedString, forKey: "attributedTitle") to customize alert title font, size, and color
  • Use action.setValue(color, forKey: "titleTextColor") to change action button colors
  • Set alert.view.tintColor for a quick global color change on non-destructive actions
  • These techniques use private API β€” test on each iOS version and accept the App Store review risk
  • For full customization without private API risk, build a custom UIViewController styled as an alert
  • Use semantic colors (UIColor.label) to support both light and dark mode

Related reading
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
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.