iOS event mechanism in layman's
April 12, 2015
This article will explain the delivery mechanism of iOS events, such as errors or different views, please note that the message. Reprinted from: http://zhoon.github.io/ios/2015/04/12/ios-event.html
There are several types of iOS events: Touch events, motion events (such as gravity and shake), remote events (such as the use of a button on a headset to control your phone), the most common of which is touch Events, which basically exist in each of the app's places, today we mainly talk about it, as for the other two events are interested in self-access to information.
On the Web page when we talk about events, we will refer to the incident response chain, we will discuss the event of the responder and the way the event is passed (bubbling), then in the app, but also inseparable from these issues, today we also weigh these aspects to introduce the iOS event mechanism: 1, when the response chain how to build? 2. How is the first responder of the event determined? 3. How did the system pass the event after the first responder was determined?
Building a response chain
No matter what kind of event, its delivery and response are closely related to the response chain, what is the response chain? There is a class in Uikit: Uiresponder, we can look at a few properties and methods of the header file:
Uiresponder is the base class for all classes that can respond to events (as the name should be), including the most common UIView and Uiviewcontroller even uiapplication, So our UIView and Uiviewcontroller are all carriers of response events.
So what does the response chain have to do with this uiresponder? The formation of event response chain and the response and transmission of events, Uiresponder have done a lot of things for us. In our app, all views are organized according to a certain structure, that is, the tree hierarchy, each view has its own superview, including the controller's topmost view (Controller's Self.view). When a view is add to the Superview, his nextresponder attribute is pointed to its superview, and when the controller is initialized, Self.view (topmost view) The Nextresponder will be directed to the controller where And the controller's Nextresponder will be pointed to Self.view Superview, so that the entire app through the Nextresponder string into a chain, which is what we call the response chain. So the response chain is a virtual chain, and there is no object to store such a chain, but to concatenate it through the properties of the Uiresponder. Such as:
Hit-testing View
At the beginning of the article, there are three types of event in iOS, in which the UIWindow will look for initial object,initial object depending on the event and the current event type. For example, Touch Event,uiwindow will first try to pass the event to the view that the event occurred, which is what is said below Hit-testview. For motion and remote Event,uiwindow, you can refer to the current FirstResponder for events such as vibration or remotely controlled, and see here for information about FirstResponder. The following is mainly about touch event Hit-testview.
With the incident response chain, the next thing to do is to find the specific responder that responds to the event, and we call it: hit-testing view, the process of looking for this view we call hit-test.
So what is hit-test, we can think of it as a detector, through which we can find and judge whether a finger is clicked on a view, or, in other words, hit-test can find the UIView on the front of the screen with the finger clicked.
Before explaining how Hit-test works, let's take a look at when it was called. Previously said Hit-test is a detector, then in the code is actually a function, UIView has the following two methods:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Whenever the finger touches the screen, UIApplication receives the event of the finger, it calls UIWindow's hittest:withevent: And see if the current clicked point is in the window, If yes, continue calling Subview's Hittest:withevent: method until you find the last view you want. After the call is finished and hit-test view is determined, the gesture attached to the view and view is associated with a Uitouch object, and this uitouch is one of the arguments passed by the event. We can see that the Uitouch header file has a view and Gesturerecognizers property, which is HitTest view and its gestures.
Now that you know when Hit-test was called, then let's see how it works. Hit-test is a recursive approach that starts from the root node of the view hierarchy and takes a look at the following diagram:
UIWindow There is a mianview,mainview inside there are three Subview:view a, view B, view C, they each have two subview, their hierarchical relationship is: View A at the bottom, view B in the middle, view c the most ( That is, the Addsubview order, the later add in the more on the above), where view a and view B have a portion of overlap. If the finger is clicked on the top of the view B.1 and the view A.2 overlap, follow the recursive way above, as shown in the order:
Recursion is to send hittest:withevent to the root node of the interface UIWindow: The message starts with a uiview, which is the HitTest view at the front of the finger's current position. When sending hittest:withevent: Message to UIWindow, hittest:withevent: The thing inside is to determine if the current click position is in the window, If you traverse the window's subview and then subview send hittest:withevent: Message (note that the message sent to Subview here is based on the current Subview index order, the larger the index is, the more it is accessed first). If the current point is not on the view, then the view's subview will not be traversed. When the event traverses the View B.1 and finds point in the View B.1, and the view B.1 does not have subview, then he is the HitTest view we are looking for and will return to the root node after it is found, and the view after view B A will not be traversed.
A picture wins thousands of words:
Note that hittest inside is to determine whether the current view supports click events, such as userinteractionenabled, hidden, alpha and other attributes, will affect whether a view can be the corresponding event, if not responding, return nil directly. We note that there is also a pointinside:withevent: method, this method is the same as the hittest:withevent: UIView is a way to determine if point is within the frame range of the view. If these conditions are met, then the traversal can go on and the code behaves as follows:
Hit-test application One, expand the view of the click Area
A button size is 10pt*10pt, if you want to enlarge the button's Click Area (Button four weeks outside the 10pt can also respond to button events), how can you do? Perhaps rewrite hittest:withevent: is a good way, Hitest is to return the view that can respond to the event, if we rewrite it in the sub-class of the button, in the method to determine if point is in the 10pt outside the button's frame, Just return to the button yourself.
Ii. passing events to Brother view
As the first diagram above, if you need to view a response event instead of B (even if the point is in the overlapping part), do nothing, when the click on the overlap, a is not responding to the event, Unless B's userinteractionenabled is no and B does not have any response function for the event. This time by rewriting B's hittest can solve this problem, in B's hittest directly return nil on the line.
Iii. Passing events to Subview
For example, the blue ScrollView setting pagingenabled so that the image stops scrolling will be fixed in the center position, if the left or right of scrollview activity, found that ScrollView is unable to scroll, The reason is that HitTest does not meet pointinside this condition, ScrollView bound only the blue area. This time rewrite UIView's hittest:withevent: And then return to ScrollView to fix the problem.
Delivery of events
With the response chain, and the first object to respond to the event is found, the next step is to send the event to the responder. There is a sendevent in uiapplication: The same method can be found in UIWindow. UIApplication is the way to send events to UIWindow, and UIWindow sends events to Hit-testview through the same interface. This can be confirmed from time Profiler:
When I click on the Wrbuybookbutton, UIWindow will go through a private method, in the inside will call the button Touchesbegan and touchesended method, Touchesbegan inside has set the button, such as the highlight of the action, This enables the delivery of events. The response of the event, which is the action that the button is bound to, is implemented in touchended by invoking the UIApplication SendAction:to:from:forEvent: method, as to how the method responds to action , you can only guess (perhaps through the OC underlying message mechanism of the relevant interface objc_msgsend to send message implementation, you can refer to the Message.h file). If the first responder does not respond to this event, the event bubbles are passed to the Nextresponder in response to the response chain.
Notice how the event was passed to Nextresponder? Take the touch event, Uiresponder inside the touch four stages of the method inside, in fact, nothing has been done, UIView inherited it to rewrite, rewrite the content is not anything, is to pass the event to Nextresponder, such as: [ Self.nextresponder touchesbegan:touches Withevent:event]. So when a view or controller does not overwrite the touch event, the event is passed on until UIApplication, which is how the event bubbles up. If the view overrides the touch method, we will generally see the effect that after the view responds to the event, the event is truncated (as in JavaScript, E.stoppropagation () is called), Its nextresponder will not receive this event, even if the Nextresponder touch method is rewritten. This time if you want the event to continue to pass, you can call [Super Touchesbegan:touches withevent:event], do not recommend the direct tune [Self.nextresponder touchesbegan:touches Withevent:event].
About the events of Uiscrollview
First say a phenomenon, we usually add to Uiscrollview (or UITableView and uicollection) above the UIButton, even if there are settings highlighted style, click on the time but found that this style is not always come out, But the event of the button can respond, very strange.
Later only to know, uiscrollview because to scroll, so the event did a special treatment: when the Uiscrollview received the event, will temporarily hijack the current event 300 milliseconds, if the finger has not been scrolled after 300 milliseconds, then think you give up scrolling, Abort the hijacking of the event and pass it down, but seeing from time Profiler that the button is not calling its own touch method, but rather invoking the touch event of its own bound gesture, because the button's highlighted style is written on the touch method of the button, So this time can not see the highlight. However, long press the button is missing can let the button have a highlight state, this is not very clear why, because from time Profiler inside the button Touchesbegan seems to have not been adjusted. If the finger scrolls within 300 milliseconds, the event will not continue to be passed to Subview, which means that the touch method of the gesture on the button will not continue to be invoked in response to the scrolling event.
This problem can be solved by a property of Uiscrollview: Delayscontenttouches, which means that the delivery of the event needs to be deferred and the default is No. After setting the delayscontenttouches to Yes, everything looks good, the button finally has a highlight style hahaha, but found another problem: if your finger clicks on the button and scrolls Uiscrollview, it will not scroll. The reason is that if the subview receives and can respond to an event (Delayscontenttouches is set to Yes) when the finger is clicked on Uiscrollview and before scrolling, the event response chain is truncated after the Subview response event. That is, the Uiscrollview itself does not respond to this event and scrolling does not occur. You can set cancancelcontenttouches to Yes to allow Uiscrollview to scroll, similar to a Touchesshouldcancelincontentview: interface, Depending on the parameter view, it is more convenient to determine if Cancel is required, and if necessary, it can be overridden in Uiscrollview subclasses.
This piece of concrete implementation of the principle we do not know, the water is too deep, only through time Profiler to see some of the approximate implementation, we do not need to delve into the general direction of understanding is good. Really interested students can also go to research, look forward to your sharing.
Resources:
1, http://smnh.me/hit-testing-in-ios/
2, http://southpeak.github.io/blog/2015/03/07/uiresponder/
3, Https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollView_Class/index.html#//apple_ ref/doc/uid/tp40006922
4, Https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/event_ Delivery_responder_chain/event_delivery_responder_chain.html
5, https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/ Gesturerecognizer_basics/gesturerecognizer_basics.html#//apple_ref/doc/uid/tp40009541-ch2-sw2
6, https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/ Gesturerecognizer_basics/gesturerecognizer_basics.html#//apple_ref/doc/uid/tp40009541-ch2-sw2
Zhoonchen
iOS event mechanism in layman's