events

Two core methods of event passing

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in boundsCopy the code

The first method returns a UIView, which is used to find out which view will eventually respond to the event, and the second method is used to determine whether a certain clicked position is within the scope of the view, and returns YES if it is

The flow of event delivery

The process description
  • We click on the screen to generate a touch event, and the system adds that event to an event queue managed by UIApplication, which takes the event from the message queue and distributes it, first to UIWindow

  • The hitTest:withEvent: method is called in UIWindow to return a view of the final response

  • In the hitTest:withEvent: method, it goes back to pointInside: withEvent: to determine whether the currently clicked point is in the UIWindow range, and if so, it traverses its subviews to find the subviews of the final response

  • The way to traverse is to traverse the subviews in reverse order, which means that the last subview added is traversed first, and in each view it calls its hitTest:withEvent: method, which is a recursive call

  • Finally, a response view is returned, and if the return view has a value, this view is the final response view, ending the whole event passing; If there is no value, then UIWindow is used as the responder

hitTest:withEvent:

The process description
  • First, the hiden attribute of the current view, whether it can interact, and whether the transparency is greater than 0.01 will be judged. If the conditions are met, the next step will be taken; otherwise, nil will be returned

  • The pointInside: withEvent: method is called to determine whether the point is within the scope of the current view, if it is, the next step, otherwise nil is returned

  • It then iterates through its subviews in reverse order, calling the hitTest:withEvent: method on each of the subviews. If one of the subviews returns a final response view, that view is returned to the caller. If all traversals are complete and no final response view is found, the current view is returned as the final response view because the click position is within the range of the current view

Example scenario

Let’s take a closer look at event passing with an example: clicking on the middle circle of a square button works, but clicking on the corners doesn’t

The idea is to modify the corresponding region in the pointInside: withEvent: method

The code is as follows:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
    if(! Self. UserInteractionEnabled | | [self isHidden] | | the self. The alpha < = 0.01) {returnnil; } // Determine whether the current view is within the click rangeif([self pointInside:point withEvent:event]) {// Iterate over the current object's subview (in reverse order) __block UIView *hit = nil; [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger IDx, BOOL * _Nonnull stop) {// convertPoint = [self convertPoint:point toView:obj]; // Call the subview hitTest method hit = [obj hitTest:convertPoint withEvent:event]if(hit) *stop = YES; }]; // Returns the current view objectreturnhit? hit:self; }else {
        returnnil; } } - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { CGFloat x1 = point.x; CGFloat y1 = point.y; CGFloat x2 = self.frame.size.width / 2; CGFloat y2 = self.frame.size.height / 2; Double dis = SQRT ((x1 - x2) * (x1 - x2) + (y1-y2) * (y1-y2)); double dis = SQRT ((x1 - x2) * (x1 - x2) + (y1-y2) * (y1-y2));if (dis <= self.frame.size.width / 2) {
        return YES;
    }
    else{
        returnNO; }}Copy the code

View’s responder chain

The first thing we need to know is that event passing is the opposite of the response process

If hitTest:withEvent: finds the initial View of the first responder, but the responder does not process the event, then the event will be passed up the responder chain: The first responder -> Superview -> view controller, if it’s passed to the top view and hasn’t handled the event, then it’s passed to UIWindow, if the Window object doesn’t handle the event, then it’s passed to UIApplication, if the UIApplication object doesn’t handle the event, Discard the event (but it does not cause a crash)

And in iOS, the objects that can respond to events are all subclasses of UIResponder, which provides four callback methods for user click, respectively corresponding to user click start, move, click end and cancel click. Among them, the cancel click event will only be called when the program forcibly exits or calls.

System callback method

UIView is a subclass of UIResponder that can override the following four methods to handle different touch events. (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event one or more fingers moved on the view, (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event one or more fingers left the view, // touches ended :(NSSet *)touches withEvent:(UIEvent *)event (void)touchesCancelled:(NSSet *) Touches withEvent:(UIEvent *) EventCopy the code

Responder chain flow chart

GitHub

Demo