Sobes.tech
Middle

How does the touch event handling mechanism work in iOS?

sobes.tech AI

Answer from AI

The touch handling mechanism in iOS is based on the Responder Chain.

Stages:

  1. The gesture (touch) is recognized by the framework (UIKit or SwiftUI).
  2. The system searches for a UIView under the touch point. This is done using the hitTest(_:with:) method in reverse order of the view hierarchy (children first, then parents).
  3. hitTest(_:with:) returns the deepest subview that can handle the event (i.e., isUserInteractionEnabled is true and the point is within the view's frame). If no such view exists, it returns nil.
  4. If hitTest(_:with:) returns a view (called the "first responder"), the system sends the touch event to it via the touchesBegan(_:with:) method.
  5. If the first responder does not handle the event (for example, does not implement the method or calls super), the event is passed to the next responder in the chain:
    • For UIView, the next responder is its superview.
    • For the root view of a controller (UIViewController.view), the next responder is its viewController.
    • For a view controller (UIViewController), the next responder is its presented view (presenter) or splitViewController, navigationController, tabBarController (depending on context).
    • For UIWindow, the next responder is the UIApplication object.
    • For UIApplication, the next responder is the application delegate (AppDelegate).
  6. This process continues until the event is handled (the method calls super) or reaches the end of the chain (usually AppDelegate).

Key methods:

  • hitTest(_:with:): UIView method to determine the view under the touch point.
  • point(inside:with:): UIView method called by hitTest to check if the point is within the view's frame.
  • touchesBegan(_:with:), touchesMoved(_:with:), touchesEnded(_:with:), touchesCancelled(_:with:): UIResponder methods for handling the touch lifecycle.

Responder Chain (example):

Deepest subview -> Superview -> ... -> Root view -> View controller -> UIWindow -> UIApplication -> AppDelegate
// Example of overriding hitTest in UIView
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // Check if the point is inside this view
    guard self.point(inside: point, with: event) else {
        return nil // Point outside this view
    }

    // Traverse subviews in reverse order (top-down)
    for subview in subviews.reversed() {
        // Convert point to subview's coordinate system
        let convertedPoint = subview.convert(point, from: self)
        // Recursively call hitTest on subview
        if let hitView = subview.hitTest(convertedPoint, with: event) {
            return hitView // Found a subview that handles the event
        }
    }

    // If no subview handles the event, return self
    return self
}
// Example of handling touch in UIViewController
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }
    let location = touch.location(in: view)
    print("Touch began at: \(location)")
    // To pass the event further down the chain, call super:
    // super.touchesBegan(touches, with: event)
}