From: Wind and Rain “83”

Preface:

In chronological order, the life cycle of an event:

The creation and transfer of the event (how the event is passed from parent control to child control and finds the most appropriate view, finds the most appropriate view implementation of the view, intercepts the handling of the event)

The key and difficult points are: 1. How to find the most suitable view 2.

Event click and important flow chart below

For more information

(1) Events in iOS

Events in iOS can be divided into three main types:

  • Touch events
  • Accelerometer event
  • Remote control event
  • Here we are only talking about touch events in iOS.

1.1. Responder Object (UIResponder)

Learning about touch events starts with an important concept – the responder object (UIResponder).

Not every object in iOS can handle events, only objects that inherit from UIResponder can accept and handle events, which we call “responder objects.” All of the following are inherited from UIResponder, so they can receive and handle events.

  • UIApplication
  • UIWindows
  • UIViewController
  • UIView

So why should a class that inherits from UIResponder be able to receive and handle events?

Because UIResponder provides the following four object methods to handle touch events.

UIResponder internally provides the following methods to handle the event touches - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; Accelerometer event - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event; - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event; - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event; Remote control events - (void) remoteControlReceivedWithEvent: UIEvent *) event;Copy the code

(2) Handling of the incident

The following uses UIView as an example to illustrate the handling of touch events.

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

Note that the above four methods are automatically called by the system, so you can override them to handle some events.

  • If two fingers touch a view at the same time, the view calls the ‘touchesBegan:withEvent’ method only once, and the ‘Touches’ argument contains two UITouch objects
  • If these two fingers touch the same view one at a time, the View calls the touchesBegan:withEvent: method two times, and each time contains a Single UITouch object in its Touches argument
  • Override the above four methods if you’re dealing with UIView touch events. You have to custom UIView subclasses that inherit from UIView. Because Apple doesn’t open source, they don’t give us a.m file for UIView. We can only handle UIView touch events by subclass inheriting the parent class and overriding the subclass methods (note: I’m talking about UIView touch events, not UIViewController touch events).
  • If you’re handling UIViewController touch events, then override those four methods in the controller’s.m file!

/ Custom UIView.h file /

#import <UIKit/UIKit.h>
 
@interface WYView : UIView
@end
Copy the code

/* Custom UIView.m file /

#import "wyview.h" @implementation WYView // this method is called once when we start touches - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{NSLog(@" touch me! ); } // this method is called very often - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{NSLog(@" movemove@createsproperty! "); ); } // this method is invoked when your fingers leave the screen - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{NSLog(@" touches continue to play!" ); } @endCopy the code

/* Controller.m file /

#import "ViewController.h" #import "WYView.h" @interface ViewController () @end@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; WYView *touchView = [[WYView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; / / background color touchView. BackgroundColor = [UIColor redColor]; // Add to parent control [self.view addSubview:touchView]; } @endCopy the code

Note: Some people say, well, if I’m dealing with the events of a controller’s own view, I don’t need to custom UIView subclasses from UIView, because I can override the touchBegan:withEvent: method in viewController.m, but, We’re talking about handling UIView touch events here, not UIViewController touch events. If you override the touchBegan:withEvent: method in viewController.m, you’re dealing with the touch event of the viewController, because the viewController also inherits from UIResponder, So it gives an illusion. So, again, if you want to handle UIView touch events, you have to custom UIView subclasses that inherit from UIView.

2.1. Drag and drop of UIView

So, how do you implement UIView drag and drop? That is, make UIView move with your finger. The following are the properties and methods of the Touches UITouch:

NS_CLASS_AVAILABLE_IOS(2_0) @interface UITouch : NSObject
 
@property(nonatomic,readonly) NSTimeInterval      timestamp;
@property(nonatomic,readonly) UITouchPhase        phase;
@property(nonatomic,readonly) NSUInteger          tapCount;   // touch down within a certain point within a certain amount of time
 
// majorRadius and majorRadiusTolerance are in points
// The majorRadius will be accurate +/- the majorRadiusTolerance
@property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0);
@property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0);
 
@property(nullable,nonatomic,readonly,strong) UIWindow                        *window;
@property(nullable,nonatomic,readonly,strong) UIView                          *view;
@property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);
 
- (CGPoint)locationInView:(nullable UIView *)view;
- (CGPoint)previousLocationInView:(nullable UIView *)view;
 
// Force of the touch, where 1.0 represents the force of an average touch
@property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);
// Maximum possible force with this input mechanism
@property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0);
Copy the code

2.1.1. UITouch objects

  • When the user touches the screen with a finger, a UITouch object is created associated with the finger

  • One finger corresponds to one UITouch object

  • If two fingers touch a view at the same time, the view calls the ‘touchesBegan:withEvent’ method only once, and the ‘Touches’ argument contains two UITouch objects

  • If these two fingers touch the same view one at a time, the View calls the touchesBegan:withEvent: method two times, and each time contains a Single UITouch object in its Touches argument

2.1.1.1. UITouch role

  • It holds information about the finger, such as the position, time and phase of the touch
  • When the finger moves, the system updates the same UITouch object so that it keeps the finger in the same touch position
  • When the finger leaves the screen, the system destroys the corresponding UITouch object

Tip: In iPhone development, avoid double-clicking events!

2.1.1.2. UITouch properties

Touch occurs in the window of the @ property (nonatomic, readonly, retain) UIWindow * window; When the touch of view @ property (nonatomic, readonly, retain) UIView * view; @Property (nonatomic, readOnly) NSUInteger tapCount; @property(nonatomic,readonly) NSTimeInterval timestamp; The current state of the touch event @Property (nonatomic, readOnly) UITouchPhase Phase;Copy the code

2.1.1.3 UITouch method

(CGPoint)locationInView:(UIView *)view; // The value returned is the position of the touch on the view // the position returned is for the view's coordinate system (starting from the top left corner of the view (0, 0)) // If the view argument is passed nil, Returns the position of the touch point in UIWindow (CGPoint)previousLocationInView:(UIView *)view; // This method records the position of the previous touch pointCopy the code

Code implementation:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event = [touches anyObject]; CGPoint curP = [touch locationInView:self]; / / get the position of a point on the CGPoint preP = [touch previousLocationInView: self]. // Get their X-axis offsets, each time relative to the last time CGFloat offsetX = curp.x-prep. // Get the y offset CGFloat offsetY = curp.y - preP. Y; / / modify control deformation or frame, center, can control controls the location of the / / deformation is relative deformation (translation) / / CGAffineTransformMakeTranslation: when was the last time will give clear before deformation, deformation parameter Settings / / make a fresh start Make: transform relative to the original position // CGAffineTransform t: transform relative to the original position // if the transform relative to the original position, pass in its transform self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY); }Copy the code

(3) the generation and transmission of events in iOS

3.1. Occurrence of events

  • When a touch event occurs, the system adds the event to a uiApplication-managed event queue. Why queue instead of stack? Since queues are characterized by FIFO, first in, first out, first-generated events are processed first, so events are added to queues.
  • UIApplication pulls the uppermost event from the event queue and dispatches the event for processing, usually first to the application’s main window (keyWindow).
  • The main window will find the most appropriate view in the view hierarchy to handle the touch event, which is the first step in the event processing process. When the appropriate view control is found, the view control’s Touches method is called for specific event handling.

3.2. Event passing

  • Touch events are passed from parent control to child control
  • UIApplication-> Window -> find the best view to handle the event

Note: If the parent control cannot accept touch events, then the child control cannot receive touch events

How does the application find the most appropriate control to handle events?

    1. First, determine whether the main window (keyWindow) can accept touch events by itself
    1. Determine if the touch point is on your body
    1. Traversal the child control from back to front in the child control array, repeat the previous two steps (so-called traversal the child control from back to front, that is, first find the last element in the child control array, and then perform steps 1 and 2)
    1. The View, for example, called fitView, will pass the event to the fitView and iterate through the fitView’s child controls until there are no more suitable views.
    1. If there is no child control that matches the criteria, then it considers itself the most appropriate view to handle the event.

There are three cases when UIView cannot receive touch events:

  • NO interaction allowed: userInteractionEnabled = NO
  • Hide: If the parent control is hidden, the child control is also hidden. The hidden control cannot accept events
  • Transparency: If you set the transparency of a control <0.01, it will directly affect the transparency of the child controls. Alpha: 0.0 to 0.01 is transparent.

Note: The default UIImageView cannot accept touch events because interaction is not allowed, i.e. UserInteractionEnabled = NO. So if you want the UIImageView to be able to interact, you need to set the UIImageView’s userInteractionEnabled = YES.

To summarize

  1. Clicking on A UIView or generating A touch event A is added to the event queue managed by UIApplication (that is, the UIApplication receives the event first).
  2. UIApplication takes the uppermost event from the event pair column (say touch event A here) and passes event A to the application’s main window (keyWindow).
  3. The window will find the most appropriate view in the view hierarchy to handle touch events. (At this point, step 1 is complete.)

If you want a view to be unable to handle an event (or the event to break when it reaches a view), you can do so in one of the three ways mentioned above. For example, set its userInteractionEnabled = NO; The events that are passed down are handled by the view’s parent control.

For example, if you don’t want the blue view to receive events, you can set the blue view’s userInteractionEnabled = NO; Then the event generated by clicking the yellow view or the blue view will be processed by the orange view, and the orange view will be the most appropriate view.

So, regardless of whether the view can handle the event or not, whenever the view is clicked it will generate the event. The key is who handles the event in the end! That is, if the blue view cannot handle the event, the touch event generated by clicking on the blue view will not be handled by the clicked view (the blue view)!

Note: Setting the transparency or hidden of the parent controls directly affects the transparency and hidden of the child controls. If the opacity of the parent control is 0 or hidden = YES, then the child control is also invisible!

3.3. (Key and difficult points) How to find the most suitable view

How does the application find the most appropriate control to handle events?

  1. First, determine whether the main window (keyWindow) can accept touch events by itself
  2. Whether the touch point is on the body
  3. Iterate through the child control from back to front, repeating the previous two steps (first looking for the last element in the array)
  4. If there is no child control that matches the criteria, then it considers itself best suited for processing

Detailing:

  1. When the main window receives the event passed by the application, it first determines whether it can handle the touch event. If so, determine whether the touch point is on the window itself
  2. If the touch point is also on the window, the window traverses its child controls from back to front (traversing its child controls just to find the best view)
  3. After traversing through each child control, the previous two steps are repeated (passing events to the child control, 1. Check whether the child control can accept the event, 2.
  4. This loop iterates through the child control until it finds the most suitable view. If there is no more suitable child control, then it becomes the most suitable view.

When the most appropriate view is found, the view’s Touches method is called to handle the specific event. So, only when the appropriate view is found and the event is passed to the appropriate View is the touches method invoked for subsequent event handling. If you don’t find the most appropriate View, you won’t call the Touches method for event handling.

Note: The reason for traversing the child controls from back to front to find the best view is just to do some circular optimization. By contrast, the view added later is on top, reducing the number of loops.

3.3.1. Find the most appropriate view underlying analysis

Two important methods: hitTest:withEvent: method pointInside method

3.3.1 hitTest: withEvent: method

When is it called?

  • As soon as an event is passed to a control, the control calls its own hitTest: withEvent: method

role

  • Find and return the most appropriate view(the most appropriate view that can respond to events)

Note: Regardless of whether the control can handle the event or whether the touch point is on the control, the event is passed to the control before the hitTest:withEvent: method is called

Interception event handling

  • Because the hitTest: withEvent: method returns the most appropriate view, you can override the hitTest: withEvent: method to return the specified view as the most appropriate view.
  • Wherever you click, the best view is the one returned from the hitTest: withEvent: method.
  • By overwriting hitTest: withEvent:, you can intercept the event and handle it as you want.

The hitTest:withEvent: method is called to whomever the event is passed to.

Note: If the hitTest:withEvent: method returns nil, then neither the control calling the method itself nor its children are the most suitable view. The most appropriate view is the parent of the control.

So the order of events is like this: Generate the touch event ->UIApplication event queue ->[UIWindow hitTest:withEvent:]-> return the most appropriate view->[child control hitTest:withEvent:]-> Return the most appropriate view

After the event is passed to the window or control, the hitTest:withEvent: method is called to find a more suitable view. So, you pass the event first, and then you find a better view on yourself based on the event.

Regardless of whether the control is the most appropriate view, the system default to first pass the event to the child control, after the child control call child control hitTest:withEvent: method verification to know whether there is a more appropriate view. Even if the parent control is the most appropriate view, the child control’s hitTest:withEvent: method will still be called, otherwise how can we know if there is a more appropriate view? That is, if the final parent control is determined to be the most appropriate view, then the hitTest:withEvent: method for the child control of that parent control is also called.

Tip: Override the parent control’s hitTest:withEvent: method to return the specified child control, or override your own hitTest:withEvent: method to return self. However, it is recommended to return the child control in the parent control’s hitTest:withEvent: as the most appropriate view!

The reason is that returning itself in its own hitTest:withEvent: method sometimes causes problems. Because there is such A situation: when traversing child controls, if not touch point for child controls A himself upon the child controls B, also require return to child controls A as the most appropriate view, adopt the method of return to their may cause had time to traverse A yourself, it is possible to traverse A little real’s view, is the B. This causes you to return not the view itself but the view where the touch point is actually located. Therefore, it is recommended to return the child control in the parent control’s hitTest:withEvent: as the most appropriate view!

For example, whiteView has two child controls, redView and greenView. RedView is added first, greenView is added later. Return self.subViews[0] from whiteView hitTest:withEvent:; return self.subViews[0] from whiteView hitTest:withEvent:; In this case return self in redView’s hitTest:withEvent: method; It doesn’t work!

#import "redview. h" @implementation redView - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ return self; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"red-touch"); }@end // or #import "whiteview.h" @implementation whiteView - (UIView *)hitTest (CGPoint) Point withEvent (UIEvent *)event{ return self.subviews[0]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"white-touch"); } @endCopy the code

Special case: No one can handle the event, nor can Windows.

  • Overwrite window’s hitTest: withEvent: method return nil

Only Windows can handle events.

  • The view of the controller’s hitTest: withEvent method return nil or the window’s hitTest: withEvent method return self

Return nil

Return nil in hitTest: withEvent: means that the view calling the current hitTest: withEvent: method is not the appropriate view, and the child controls are not the appropriate view. If the sibling control does not have an appropriate view, then the most appropriate view is the parent control.

Find the most appropriate hitTest: withEvent: method for view low-level profiling

HitTest :withEvent: the underlying implementation of the method

#import "wywindow.h" @implementation WYWindow when: as soon as an event is passed to a control, UIApplication -> [UIWindow hitTest:withEvent:] Find the most appropriate view to tell the system // point: the current finger is touching the point // point: is the point in the method caller's coordinate - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{// 1. Window can receive events under the if (self userInteractionEnabled = = NO | | the self. The hidden = = YES | | the self. The alpha < = 0.01) return nil. If ([self pointInside:point withEvent:event] == NO) return nil; Int count = (int)self.subviews.count; for (int i = count - 1; i >= 0; UIView *childView = self. Subviews [I]; ChildP = [self convertPoint:point toView:childView]; UIView *fitView = [childView hitTest:childP withEvent:event]; If (fitView) {// Return fitView; }} return self;}} return self; } - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event //{  // return NO; //} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); } @endCopy the code

The hit: withEvent: method calls pointInside:withEvent: method to determine whether the point is in the method caller’s coordinate system.

3.3.1.2 pointInside: withEvent: method

PointInside :withEvent: method determines whether the point is in the current view (method caller’s coordinate system). If YES is returned, the point is in the method caller’s coordinate system. Returning NO means that the point is not in the method caller’s coordinate system, and the method caller cannot process the event.

Practice 3.3.2 rainfall distribution on 10-12.

So now we have a viewA on the screen, and viewA has a subView called viewB, and when I touch viewB,viewB will respond to the event, and when I touch viewA, it won’t respond to the event. How to do that?

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self) {
        return nil;
    }
    return view;
}
Copy the code

(4) Response to the event

4.1. The overall process of touch event processing

  1. A touch event generated when the user clicks on the screen is passed through a series of processes to find the most appropriate view control to handle the event

  2. After finding the most appropriate view control, we call the control’s Touches method to handle the specific event, ‘touchesBegan… TouchesMoved… TouchedEnded…

  3. The default of these touches is to pass the event up the responder chain (the Touch method defaults to not handling the event, but only passing the event), handing the event to the previous responder for handling

4.2. Schematic diagram of responder chain

Responder chain: In iOS applications both in the UIWindow below and the front of a button, and they put there is a relationship between before and after, a control can be placed above or below another control, so when the user clicks on a widget is triggering control above or below, this has relations constitute a chain is called “the responder chain”. In other words, a responder chain is a chain connected by multiple responder objects. The relationship of the responder chain in iOS can be expressed as follows:

Responder object: An object that can handle events, that is, an object inherited from UIResponder

Function: Can clearly see the relationship between each responder, and can allow an event to be handled by multiple objects.

How to determine the last responder

  1. If the current view is the controller’s view, then the controller is the last responder
  2. If the current view is not the view of the controller, the parent control is the previous responder

Event transfer process of responder chain:

  1. If the current view is the controller’s view, then the controller is the last responder and the event is passed to the controller; If the current view is not the view of the controller, then the parent view is the last responder of the current view and the event is passed to its parent view
  2. At the top level of the view hierarchy, the view passes the event or message to the Window object for processing if it cannot process the received event or message either
  3. If the Window object does not process either, it passes the event or message to the UIApplication object
  4. If THE UIApplication also cannot handle the event or message, it is discarded

Summary of the whole process of event processing:

  1. After the touch screen generates a touch event, the touch event is added to the event queue managed by UIApplication (that is, UIApplication receives the event first).
  2. UIApplication pulls the uppermost event from the event queue and passes the event to the application’s main window (keyWindow).
  3. The main window will find the most appropriate view in the view hierarchy to handle touch events. (At this point, step 1 is complete.)
  4. The most appropriate view calls its own Touches method to handle the event
  5. The default is to throw events up the responder’s chain.

The default of touches:

#import "wyview. h" @implementation WYView // As soon as the control is clicked, touchBegin will be called. If we hadn't overwritten this method, we wouldn't have handled the touch event (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{// the view is created when the view is created touchesBegan:touches withEvent:event]; // Create views (); // Create views (); // Create views (); // Create views ()Copy the code

Event delivery and response:

  1. UIApplication -> UIWindow -> UIView -> Initial View when an event occurs, the event is passed from the parent control to the child control, namely UIApplication -> UIWindow -> UIView -> Initial View.

  2. Next comes the response to the event. If the initial View does not handle the event, it will pass the event to its parent view (the superView of the Inital View). If the superior view still cannot be processed, it will continue to be passed up; All the way to view controller view Controller, first check whether the view controller’s root view can handle this event; If not, it then determines whether the view controller can handle the event. If not, it continues to pass up. (If the second graph view controller itself is still in another view controller, it continues to be handed over to the root view of the parent view controller, or if the root view cannot handle it, it is handed over to the parent view controller); All the way to The Window, if the Window still can’t handle the event, it continues to be passed to the application, and if the Application still can’t handle the event, it is discarded

  3. In the response to the event, if a control implements Touches… Method, the event will be accepted by the control if [super Touches…] is called; It passes the event up the responder chain, to the last responder; It then calls the last responder’s touches… Method.

How to handle multiple objects in an event: Since the system defaults to toting the event to the parent control, you can override its own touches method and the parent’s Touches method to handle multiple objects in an event.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ // 1. Deal with events first... NSLog(@"do somthing..." ); [super touchesBegan: Touches withEvent:event] }Copy the code

Summary of the whole process of event processing:

  1. After the touch screen generates a touch event, the touch event is added to the event queue managed by UIApplication (that is, UIApplication receives the event first).
  2. UIApplication pulls the uppermost event from the event queue and passes the event to the application’s main window (keyWindow).
  3. The main window will find the most appropriate view in the view hierarchy to handle touch events. (At this point, step 1 is complete.)
  4. The most appropriate view calls its own Touches method to handle the event
  5. The default is to throw events up the responder’s chain.