iOS
UIView
TouchEvent
Controller
Swift

UIView touch event in controller

Master System Design with Codemia

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

Introduction

Touch handling in iOS can live in a UIView, a UIViewController, or a gesture recognizer, but those options are not interchangeable. If the interaction belongs to one specific visual element, handle it in the view or use a recognizer attached to that view. If you override raw touch methods in the controller for everything, the code usually becomes harder to reason about and easier to break.

Where Touch Events Actually Go

UIView inherits from UIResponder, which means it can receive touch callbacks such as:

  • 'touchesBegan'
  • 'touchesMoved'
  • 'touchesEnded'
  • 'touchesCancelled'

The event is usually delivered to the view hit by the touch, not directly to the controller. A controller can also override these methods because it is in the responder chain, but that is usually not the first design choice for control-specific interaction.

Preferred Option: Use a Gesture Recognizer

For taps, pans, long presses, and similar common gestures, a recognizer is more maintainable than overriding raw touch methods.

swift
1import UIKit
2
3final class DemoViewController: UIViewController {
4    private let box = UIView()
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8
9        box.frame = CGRect(x: 60, y: 120, width: 160, height: 160)
10        box.backgroundColor = .systemBlue
11        view.addSubview(box)
12
13        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
14        box.addGestureRecognizer(tap)
15    }
16
17    @objc private func handleTap() {
18        print("box tapped")
19    }
20}

This keeps the controller involved without forcing it to manually decode raw touch phases.

Use a Custom View for Low-Level Touch Logic

If the interaction is custom drawing, drag tracking, or fine-grained touch state, put that behavior in a UIView subclass.

swift
1import UIKit
2
3final class TouchView: UIView {
4    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
5        super.touchesBegan(touches, with: event)
6        backgroundColor = .systemGreen
7    }
8
9    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
10        super.touchesEnded(touches, with: event)
11        backgroundColor = .systemBlue
12    }
13}

And then use it from the controller:

swift
1import UIKit
2
3final class DemoViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        let touchView = TouchView(frame: CGRect(x: 40, y: 100, width: 200, height: 200))
8        touchView.backgroundColor = .systemBlue
9        view.addSubview(touchView)
10    }
11}

This keeps the touch behavior where the touched UI actually lives.

When the Controller Should Handle Touches Directly

Direct controller overrides make sense when the whole screen is the interaction surface and there is no better view abstraction.

swift
1import UIKit
2
3final class DemoViewController: UIViewController {
4    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
5        super.touchesBegan(touches, with: event)
6
7        if let touch = touches.first {
8            let point = touch.location(in: view)
9            print("touch began at \\(point)")
10        }
11    }
12}

That works, but it is best reserved for screen-level behavior, not for emulating button or subview logic that should be localized.

Coordinate Systems Matter

A frequent source of confusion is using the wrong coordinate space. touch.location(in:) returns the touch position in whichever view you pass.

swift
1if let touch = touches.first {
2    let inRoot = touch.location(in: view)
3    let inSubview = touch.location(in: someSubview)
4    print(inRoot, inSubview)
5}

If gesture math looks wrong, check the view passed to location(in:) before debugging anything else.

Gesture Recognizers Versus Raw Touches

Use gesture recognizers when:

  • the interaction matches a common gesture
  • you want reusable, testable behavior
  • you want the system to manage recognition state

Use raw touch methods when:

  • you need fine-grained touch phase control
  • you are building a drawing surface or custom interaction model
  • gesture recognizers are too high-level for the task

That distinction keeps UIKit code more predictable.

Common Pitfalls

  • Handling subview-specific touches in the controller instead of the view or a gesture recognizer.
  • Forgetting to enable user interaction on the target view when needed.
  • Using the wrong coordinate system in location(in:).
  • Overriding touch methods without calling super when the responder chain still matters.
  • Rebuilding tap or pan logic manually instead of using the built-in recognizers.

Summary

  • Touches are usually delivered to the view that was hit, not directly to the controller.
  • For common interactions, prefer gesture recognizers.
  • For custom low-level touch behavior, use a UIView subclass.
  • Controller-level raw touch overrides are best for screen-wide interaction, not fine-grained subview behavior.
  • Most touch bugs are architectural or coordinate-space mistakes rather than UIKit mysteries.

Course illustration
Course illustration

All Rights Reserved.