Objective: to understand the event transmission process and response mechanism, which can help deal with some gesture conflicts, custom gestures and other problems
###1. Event classification?
1.1 in iOS system, events are divided into four categories:
- UIEventTypeTouches: touch phone screen events
- UIEventTypeMotion: mobile phone shaking and motion events. For example, when shaking the mobile phone, the mobile phone gyroscope senses that the event is triggered by UIKit, so it does not comply with the event response mechanism.
- UIEventTypeRemoteControl: mobile phone remote control event. It is mainly used to receive commands from external devices such as headphones. The purpose is to control the multimedia of mobile phones.
- Uieventtypepress: physical pressing events, such as volume, switch and physical key.
1.2 introduction to uitouch
@property(nonatomic,readonly) NSTimeInterval timestamp; //Time when the event occurred @property(nonatomic,readonly) UITouchPhase phase; // Is a constant indicating whether the touch starts, moves, ends, or cancels. Description of the possible values for this property @property(nonatomic,readonly) NSUInteger tapCount; // touch down within a certain point within a certain amount of time @property(nonatomic,readonly) UITouchType type API_AVAILABLE(ios(9.0));// Touch type @property(nullable,nonatomic,readonly,strong) UIWindow *window;// Window where the event occurs @property(nullable,nonatomic,readonly,strong) UIView *view; // View in which the event occurred - (CGPoint)locationInView:(nullable UIView *)view; // Current touch position - (CGPoint)previousLocationInView:(nullable UIView *)view; // Previous position
1.2.1 the above is the key fields and methods of the UITouch class.
- When a finger touches the screen, a UITouch object will be created, and a finger corresponds to a UITouch object.
- If two fingers touch the screen at the same time, view will only call the touchesbegan: (nsset < UITouch * > *) touches withevent: (uievent *) event method once. There are two UITouch objects in the touches parameter. If two mobile phones touch the screen one after the other, view will call touchesbegan: (nsset < UITouch * > *) touches withevent: (uievent *) event twice, with only one UITouch object at a time.
- When you want to touch UIView with multiple fingers at the same time and UIView can accept it, you need to manually turn on the value of multipleTouchEnabled is YES. UIView does not support multi-click by default.
1.2.2 introduction to important attributes and methods of uitouch
- You can get the interval of events through timestamp and customize special response events.
- tapCount is the number of clicks on the screen in a certain period of time. According to this, you can customize the double-click and three click events.
- locationInView: the position of the current touch point in the view.
- previousLocationInView: the last touch point is at the position of the view.
- The above two methods can complete the drag and drop function of view.
1.3 introduction of uiresponder
UIResponder class inherits NSObject. In iOS system, only UIResponder can respond to events. In iOS system, UIApplication, UIViewController and UIView directly inherit UIResponder class, so they can respond to events directly. In particular, UIWindow inherits UIView, so UIWindow can also respond to events. In actual development, we can rewrite the methods provided by UIResponder to complete specific requirements. The following is part of the source code of UIResponder:
@interface UIResponder : NSObject <UIResponderStandardEditActions> // Get the next response chain @property(nonatomic, readonly, nullable) UIResponder *nextResponder; // Specifies whether the current view can be the first response event. It is not by default @property(nonatomic, readonly) BOOL canBecomeFirstResponder; - (BOOL)becomeFirstResponder; // Whether the current view can be registered as the first responder is yes by default. @property(nonatomic, readonly) BOOL canResignFirstResponder; , - (BOOL)resignFirstResponder; @property(nonatomic, readonly) BOOL isFirstResponder; // Touch the status corresponding to the screen - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches ; // The corresponding status of the phone's own physical keys - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event ; - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event ; - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event ; - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event ; // Shaking state of mobile phone - (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event ; - (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event ; - (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event ; // Remote status - (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event ;
1.4 introduction to uitapgesturerecognizer
The UITapGestureRecognizer class is in IOS3 2, which makes it easier for developers to deal with touch screen events. UITapGestureRecognizer has seven subclasses, which can help us deal with common requirements. For example, UITapGestureRecognizer can recognize gesture events in the pictures in UITabelViewCell.
- UITapGestureRecognizer: single point or multi-point gesture recognizer
- UIPinchGestureRecognizer: zoom gesture recognizer
- UIRotationGestureRecognizer: rotating gesture recognizer
- Uiswapegestuurerecognizer: sliding gesture recognizer
- Uipangestrurerecognizer: Pan gesture recognizer
- Uiscreen edgepangestuurerecognizer: Pan gesture starting near the edge of the screen
- UILongPressGestureRecognizer: long press gesture recognizer
Part of the source code of UIGestureRecognizer is as follows:
@interface UIGestureRecognizer : NSObject // Valid action method signatures: // -(void)handleGesture; // -(void)handleGesture:(UIGestureRecognizer*)gestureRecognizer; - (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action NS_DESIGNATED_INITIALIZER; // designated initializer - (instancetype)init; - (nullable instancetype)initWithCoder:(NSCoder *)coder; // Add behavior for gesture recognition - (void)addTarget:(id)target action:(SEL)action; // Remove gesture behavior - (void)removeTarget:(nullable id)target action:(nullable SEL)action; // You can use state to distinguish the seven subclasses of UIGestureRecognizer @property(nonatomic,readonly) UIGestureRecognizerState state; // the current state of the gesture recognizer // The agent of gesture recognition can handle the behavior of different gestures @property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate; // the gesture
2. Event generation and transmission
2.1 event transmission rules: the transmission of events is from the parent view to the child view, so if the parent control cannot receive the touch event, the child control cannot receive the touch event. The process is roughly as follows.
- When the user touches the screen, the system will add the touch event to a queue event managed by UIApplication.
- UIApplication will take the first event from the event queue and distribute the event for processing. Usually, it will send the event to the application's main window first.
- The main window will find the most appropriate view in the view hierarchy to handle touch events.
2.1 for example, the following figure shows the view hierarchy.
2.2 according to the event transmission rules, follow the click example in Figure 2.1.
- Click 1: if this contact is on Bview, its event distribution is: UIApplication – > UIWindow – > aview – > Bview.
- Click 2: this contact is on DView, and its event distribution is: UIApplication – > UIWindow – > aview – > bview – > c1view – > DView.
- Click 3: if this contact is on C2View, its event distribution is: UIApplication – > UIWindow – > aview – > bview – > C2View.
####2.3 UIView cannot handle touch events
- Interaction not allowed: userInteractionEnabled = NO
- hidden=yes: if the parent control is hidden, the child control will also be hidden, and the hidden control cannot accept events
- Transparency apha: if the transparency of a control is set to < 0.01, it will directly affect the transparency of child controls. alpha: 0.0 ~ 0.01 is transparent.
Note: if * * satisfies one of the above points, the touch event will not continue to be transmitted. At this time, it will be handled by its parent control** For example, for click 2, assume that DView is set to disallow interaction, that is, userInteractionEnabled = NO. Then the touch event of clicking 2 will be handled by C1View.
2.4 question 1: how does iOS determine the most appropriate receiving control?
The general process is as follows:
1. After receiving the event from the application, the main window first determines whether it can receive the hand touch event. If yes, then judge whether the touch point is on the window itself, and execute step 2, otherwise discard this event.
2. If the touch point is also on the window, the window will traverse its own child controls from back to front. (personal understanding of traversal from back to front: combined with the event response mechanism, it is opposite to the transmission path of the event transmission mechanism and is transmitted inward to the end of UIApplication. Therefore, traversal from back to front here reduces the number of unnecessary traversals. Assuming that the current view is the view most suitable for receiving events, it is not necessary to traverse the parent view or parent view of the view, so as to reduce the number of traversals, and the transmission of response events to the view also stops) .
3. After traversing each child control, judge whether there are child controls, and then repeat the above two steps: pass events to child controls, 1 Judge whether the child control can accept events, 2 The point is not on the child control.
4. In this way, loop through the child controls until you find the most appropriate view to receive events. If there is no more appropriate child control after traversal, you will become the most appropriate view to receive events.
2.5 theoretical basis of question 1
#####2.5.1 hitTest: withEvent: method
When an event is passed to a control, whether the control can handle the event or not, if the contact is not on the control, the control will first call its own
- (UIView *)hitTest:(CGPoint)point withEvent: (UIEvent *)event
Method to find the most suitable view for receiving events.
The official introduction of this method is as follows:
- This method traverses the view hierarchy by calling the pointInside: withEvent: method of each subview to determine which subview should receive a touch event.
- If pointInside: withEvent: returns YES, then the subview's hierarchy is similarly traversed until the frontmost view containing the specified point is found.
- If a view does not contain the point, its branch of the view hierarchy is ignored.
The official suggestion is that we don't need to call it explicitly, we just need to rewrite it to achieve special functions, such as shielding child controls from accepting events.
From the above description:
- Method hitTest: Event: first call pointInside: withEvent to determine whether the recipient can accept the touch event. If you can call hitTest: Event: of the child control, repeat until you find the most appropriate view to accept the event.
- If the return value of pointInside: withEven: is YES, the touch event will be transmitted to the sub view, and the hitTest: Event: of the sub view will also be called, so the hitTest: Event: of the sub view will be called by default.
- Override this method to customize how events are passed. As long as the method returns nil, the event will not be passed down, and the parent control is considered to be the most suitable view to receive the event.
#####2.5.2 method pointInside: withEven:
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event;
It should be emphasized that if the child view exceeds the bounds of the parent view Then the view beyond the part will not receive the touch event.
####2.6 combining these two methods can do some special functions
- Transfer the response event. For example, click Aview to let BView respond.
- Click the child view to make the parent view respond.
- An event can be responded by multiple view s.
3. Introduction to response chain transmission
The above describes event delivery, and its results find the most appropriate view to receive events. The event response starts with the most suitable view. Official website link.
3.1 delivery rules
* * if the current response control does not handle the event, the event will be passed up the response chain. If it responds to the event, it will consume the event and stop passing on the response chain. If the response chain is passed to UIApplication and has not been processed, it will be discarded. It is the opposite of finding the most appropriate View** Figure 3.1 shows an example of the response chain introduced on Apple's official website.
Figure 3.1 schematic diagram of click event response chain
* * explanation: * * if the child View (UILabel, UITextField, UIBUtton) does not process events, UIKit sends events to the parent UIView object, followed by the root View of the window (UIWindow). Before directing the event to the window, the responder chain is transferred from the root View to the owning View controller. If the window cannot handle the event, UIKit passes the event to the UIApplication object. In particular, if the object is an instance of UIResponder and is not part of the responder chain, it may also be passed to the application delegate. (stop event delivery if it can be handled).
* * special events: * * motion events related to accelerometers, gyroscopes and magnetometers do not follow the response chain. Instead, Core Motion passes these events directly to the specified object.
**Novice's article may be wrong, please correct it.