iOS development
UIControl subclassing
Swift programming
user interface
mobile app development

How to correctly subclass UIControl?

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

Subclass UIControl when you want a reusable interactive component that should participate in target-action events like a built-in control. A correct subclass needs clear state management, predictable touch handling, and a clean contract for when it emits events such as .valueChanged.

Start With A Clear Control State Model

A custom control should centralize its state and update its appearance from that state:

swift
1import UIKit
2
3final class TogglePillControl: UIControl {
4    private let titleLabel = UILabel()
5
6    private(set) var isOn = false {
7        didSet {
8            updateAppearance()
9            sendActions(for: .valueChanged)
10        }
11    }
12
13    override init(frame: CGRect) {
14        super.init(frame: frame)
15        commonInit()
16    }
17
18    required init?(coder: NSCoder) {
19        super.init(coder: coder)
20        commonInit()
21    }
22
23    private func commonInit() {
24        addSubview(titleLabel)
25        titleLabel.textAlignment = .center
26        layer.cornerRadius = 16
27        updateAppearance()
28    }
29
30    private func updateAppearance() {
31        backgroundColor = isOn ? .systemGreen : .systemGray5
32        titleLabel.text = isOn ? "On" : "Off"
33    }
34}

That pattern keeps the control's visual output tied directly to its state instead of scattering rendering logic across several methods.

Handle Touch Tracking Deliberately

For UIControl, the tracking methods are usually the right place to interpret touches:

swift
1extension TogglePillControl {
2    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
3        isHighlighted = true
4        return true
5    }
6
7    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
8        defer { isHighlighted = false }
9        guard let touch = touch else { return }
10
11        if bounds.contains(touch.location(in: self)) {
12            isOn.toggle()
13        }
14    }
15
16    override func cancelTracking(with event: UIEvent?) {
17        isHighlighted = false
18    }
19}

This gives behavior that feels much closer to system controls than bolting a tap gesture recognizer onto a plain UIView.

Expose Standard Control Events

The point of UIControl is not only touch handling. It is integration with the target-action system:

swift
toggle.addTarget(self, action: #selector(didChangeValue), for: .valueChanged)

If your control represents a value change, emit .valueChanged consistently. If it behaves more like a button tap, .touchUpInside may be the relevant event instead.

That event contract is part of the control API and should be easy for other developers to understand.

Support Layout And Accessibility

A reusable control should participate cleanly in Auto Layout:

swift
1extension TogglePillControl {
2    override var intrinsicContentSize: CGSize {
3        CGSize(width: 120, height: 40)
4    }
5
6    override func layoutSubviews() {
7        super.layoutSubviews()
8        titleLabel.frame = bounds.insetBy(dx: 8, dy: 4)
9    }
10}

And it should expose accessibility metadata:

swift
isAccessibilityElement = true
accessibilityTraits = .button
accessibilityValue = isOn ? "On" : "Off"

That keeps the control usable for VoiceOver and other assistive technologies.

Respect Built-In State Like isEnabled And isHighlighted

System controls change appearance when highlighted or disabled. Your custom control should do the same:

swift
1override var isEnabled: Bool {
2    didSet {
3        alpha = isEnabled ? 1.0 : 0.5
4    }
5}
6
7override var isHighlighted: Bool {
8    didSet {
9        transform = isHighlighted ? CGAffineTransform(scaleX: 0.98, y: 0.98) : .identity
10    }
11}

These inherited properties exist for a reason. Reusing them makes the control feel native.

Common Pitfalls

One common mistake is subclassing UIView and then manually re-creating control semantics that UIControl already provides.

Another issue is mutating internal state without emitting the correct control events, which makes the control hard to integrate with the rest of UIKit.

A third problem is combining layout logic, drawing logic, and touch logic in one large method instead of separating responsibilities.

Finally, custom controls often skip accessibility until late in the project, even though it should be designed in from the start.

Summary

  • Subclass UIControl when you want reusable target-action behavior, not just custom drawing.
  • Centralize state and update appearance from that state.
  • Use the tracking methods for touch handling instead of ad hoc gesture logic.
  • Emit the correct control events such as .valueChanged.
  • Support layout, accessibility, and built-in states so the control behaves like a real UIKit component.

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.