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
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.
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
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
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
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)
Custom Alert View (Recommended for Full Customization)
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