The basic concept

  • Responder: it is UIResponder/UIView/UIViewController/UIApplication instance. It accepts the event, and it must either process the event or pass it on to the next responder. UIKit will automatically decide which object is the most appropriate responder, the first responder.
  • Response chain: The process by which a responder delivers an event.

The flow of the response chain

Take the official document as an example:

If the Text field doesn’t handle the event, UIKit passes the event to its parent UIView object, which in turn passes it to the root view of UIViewController, or if it can’t handle it, to UIWindow. If the Window cannot handle the event, the event is passed to UIApplication.

How do I determine which responder contains the Touch event

UIKit uses view-based hit-testing to determine where touch events occur. After a touch event occurs, UIKit compares whether the view where the touch is located is in the view hierarchy. HitTest (_:with:) turns the topmost view containing the touch event into the first responder.

The hitTest(_:with:) method ignores all subviews of the view if the touch position is outside the view boundary. Therefore, when a view’s clipsToBounds is false, the subview will not respond to the event even if the touch position is within the bounds of the subview.

The hitTest(_:with:) method ignores all subviews of the view if the touch position is outside the view boundary:

If we have the above hierarchy, blueView is a child of greenView, grayView is a child of blueView, and UITapGestureRecognizer gesture is added to all three views. If you click on the part of the grayView that is outside of the blueView part it will not respond to the grayView action because that part is outside of the blueView part, so the blueView and all its child views will be ignored by the response chain. So, if you click on the part of the grayView beyond the blueView, the responder is the greenView, and it responds to the greenView action.

When a touch event occurs, UIKit creates a UITouch object and associates it with a view. Note that UIKit updates the UITouch object when the touch position or other parameters change. But the View property of the UITouch object does not change. The view property value of the UITouch object does not change even if the UITouch object is positioned beyond the boundary of the original view. When touch ends, UIKit releases the UITouch object.

hitTest(_:with:)

This method is used to return the topmost subview containing the current point in the view layer that receives the Touch event.

It uses the point(inside:with:) function to find the subview containing point, and if point(inside:with:) returns true, it goes all the way up to the topmost subview containing point.

You can override it to hide response events from certain subviews. Views are ignored when their hidden is true, isUserInteractionEnabled is false, or alpha is less than 0.01.

How do I modify the response chain

You can modify the response chain by overriding the next attribute of the responder. After you modify, the next responder is the object you returned.

Here are the default next responders for UIKit:

  • UIView: If view is the root view of Controller, the next responder is Controller; If it is not the root view, the next responder is its parent view.
  • UIViewController: If Controller is the root view of the window, the next responder is the window object; If Controller1 is presented by Controller2, the next responder is Controller2.
  • UIWindow: The next responder is the UIApplication object,
  • UIApplication: When the App delegate is an instance of UIResponder and is not a View, view Controller, or app itself. The next responder to UIApplication is app Delegate.

About the UIControl

If we add a button with an action on top of the view we’re adding a gesture to, will clicking on the button trigger the view’s gesture event or the button’s action?

The answer is Button’s action. The gesture handler does not affect UIKit Controls’ ability to handle events.

Not only UIButton, the following objects are not affected by gesture recognizers:

  • UISwitch, UIStepper, UISegmentedControl, UIPageControl, UISlider UISwitch

These are all subclasses of UIControl.

The practical application

Expand the range of UIButton clicks

  • Create a UIButton subclass and override the point(inside:with:) method
// Expand the scope of the button by 10pt. Be careful that the scope does not exceed the boundary of the superview. override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {let newFrame = CGRect(x: bounds.origin.x - 10, y: bounds.origin.y - 10, width: bounds.size.width + 20, height: bounds.size.height + 20)
    return newFrame.contains(point)
}
Copy the code
  • Set the contentEdgeInsets property of a button
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
Copy the code

Resolve the conflict between scrollView and subclass TableView/CollectionView right slide gesture and system right slide return gesture

// Invalidate gesture responses of scrollView and subclasses by modifying responder dependenciesif letrec = navigationController? .interactivePopGestureRecognizer { collectionView.panGestureRecognizer.require(toFail: rec) }Copy the code

conclusion

  • How to find the first responder:
UIWindow receives an event and executes hit-test to find the object that should receive the event. HitTest :withEvent will determine if the view contains the location of the current event by calling pointInside :withEvent:. Call hitTest:withEvent recursively until you find the topmost object that contains point, that is, the first responder.Copy the code
  • The process of passing a response chain when an event cannot be handled
subview -> superview ... -> root view -> view controller -> root view controller -> window -> UIAPPlication - app delegate
Copy the code
  • The ability of UIControl’s subclasses to handle events is unaffected by gesture recognizers

reference

  • Using Responders and the Responder Chain to Handle Events
  • Attaching Gesture Recognizers to UIKit Controls
  • How can I increase the Tap Area for UIButton?
  • iOS Event Delivery Introduces And Example