What does it do?
What is hitTest?
In iOS world, the to determine whether a view can be interactive with user, the view has a function called
hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
.
Once there’s a touch event from user, the system will call this function to tell the view whether it should trigger touch event or gesture recognizer.
What does it do?
Basically the function returns “The frontmost view that is hit”.
How does it do it?
Since we don’t know the actual implementation of it, we can only simulate the rule of it with following code.
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !point(inside: point, with: event) {
return nil
}
if alpha < 0.01 || !isUserInteractionEnabled || isHidden {
return nil
} for subview in subviews.reverse() {
if let hitView = subview.hitTest(point, with: event) {
return hitView
}
}
return nil
}
As we can see the following steps…
- check if the hit is inside the view.
- check alpha, userInteraction flag, isHidden.
- pass it to its subview.
Behaviors
1. alpha < 0.01, isUserInteractionEnabled = false, isHidden -> Return nil
As we can see in the code, if one the 3 conditions above is true, then the hitTest function will return nil. But what does return nil mean?
2. Return nil -> no touch events or gesture will be triggered
If the function returns nil, the view who owns the function will not be triggered any event including touchInside, gesture recognition.
3. Return nil -> Won’t pass event to subviews
If you override the function and return nil
programmatically, it won’t pass the event to the hitTest of its subviews.
4. Return nil -> The parent view will active its touch events, gestures
If we return nil for the current view, its parent view will become the end node of the touch chain, then the parent will trigger events.
Practice — How do we use it?
So after the understanding of the function, how do we use it?
Pass the event under current view.
If we don’t want the white view to do anything, we can simply douserInteractionEnable = false
. But this way, the gray view will not be able to receive events.
Instead, we can override the hitTest in White View as below:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
{
let view = super.hitTest(point, with: event)
if view == self {
return nil
}
return view
}
In this way, the view will pass the event to its parent view because it return nil when touch in it. But will pass on to subview if the final hitTest wasn’t on it.