Event handling for iOS - how hitTest:withEvent: and pointInside:withEvent: are related?
It seems quite a basic question. But I agree with you the document is not as clear as other documents, so here is my answer.
The implementation of hitTest:withEvent:
in UIResponder does the following:
- It calls
pointInside:withEvent:
ofself
- If the return is NO,
hitTest:withEvent:
returnsnil
. the end of the story. - If the return is YES, it sends
hitTest:withEvent:
messages to its subviews.
it starts from the top-level subview, and continues to other views until a subview
returns a non-nil
object, or all subviews receive the message. - If a subview returns a non-
nil
object in the first time, the firsthitTest:withEvent:
returns that object. the end of the story. - If no subview returns a non-
nil
object, the firsthitTest:withEvent:
returnsself
This process repeats recursively, so normally the leaf view of the view hierarchy is returned eventually.
However, you might override hitTest:withEvent
to do something differently. In many cases, overriding pointInside:withEvent:
is simpler and still provides enough options to tweak event handling in your application.
hitTest:WithEvent and Subviews
You're not traversing the view hierarchy. From the documentation:
This method traverses the view hierarchy by sending the pointInside:withEvent: message to each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored.
You only need to implement pointInside:withEvent:
. You shouldn't override hitTest:withEvent:
because the standard implementation will call your implementation of pointInside:withEvent:
and do for you all the hard work of traversing the hierarchy.
Implement like this:
@interface TestView1 : UIView
@end
@implementation TestView1
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat radius = 100.0;
CGRect frame = CGRectMake(0, 0,
self.frame.size.width + radius,
self.frame.size.height + radius);
return (CGRectContainsPoint(frame, point));
}
@end
@interface TestView2 : UIView
@end
@implementation TestView2
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat radius = 100.0;
CGRect frame = CGRectMake(0, 0,
self.frame.size.width + radius,
self.frame.size.height + radius);
return (CGRectContainsPoint(frame, point));
}
@end
Now, whether you need both the views is up to you. You had already succeeded in expanding touchable area of v1, and with the code above you can do it with v2 as a subview of v1.
However, TestView1
and TestView2
implementations are exactly the same (even in your original post), so why do you need to have them separate? You could make v1 and v2 both instances of the same class, e.g. TestView
, and instantiate them as follows:
TestView *v1 = [[TestView alloc] initWithFrame:CGRectMake(50.f, 50.f, 100.f, 100.f)];
[self.view addSubview:v1];
v1.clipsToBounds = YES;
TestView *v2 = [[TestView alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f)];
v2.backgroundColor = UIColor.yellowColor;
[v1 addSubview:v2];
Swift hitTest:point withEvent to detect subview
It looks like you hitTest is never checking to see if the point is in the view. Give this a whirl:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// if there are subviews, step through them
// if the subview contains the point, call hitTest on the subview to see if the subview's subviews contain the point
// if they do, return the result,
// if they don't, return the subview that does contain the point.
for subview in self.subviews.reversed() {
let subPoint = subview.convert(point, from:self)
if subview.point(inside: subPoint, with: event) == true {
if let result = subview.hitTest(subPoint, with: event) {
return result
} else {
return subview
}
}
}
// none of the subviews contain the point,(or there are no subviews) does the current view contain the point?
// if yes, return self. otherwise, return nil
if self.point(inside: point, with: event) == true {
return self
} else {
return nil
}
}
pointInside:withEvent inside uibezierpath (overlapping area between two views)
You have to implement hitTest method of UIView to detect touch on particular view. Simply subclass your View and implement hitTest method.
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if ([path containsPoint:point]){
return [super hitTest:point withEvent:event];
}
else{
return nil;
}
}
Related Topics
@Property Retain, Assign, Copy, Nonatomic in Objective-C
Difference Between Uiviewcontentmodescaleaspectfit and Uiviewcontentmodescaletofill
Always Pass Weak Reference of Self into Block in Arc
When and Why Should You Use Nsuserdefaults'S Synchronize() Method
Command Failed Due to Signal: Segmentation Fault: 11
How to Add Objects to a Uiscrollview That Extend Beyond Uiview from Storyboard
Adding a View Controller as a Subview in Another View Controller
Get Notified When Uitableview Has Finished Asking for Data
Uipageviewcontroller Gesture Recognizers
How to Convert an Nsstring Value to Nsdata
Make Uinavigationbar Transparent
Event Handling For iOS - How Hittest:Withevent: and Pointinside:Withevent: Are Related
How to Crop a Uiimageview to a New Uiimage in 'Aspect Fill' Mode
Detect iOS App Entering Background
Add Image to Uialertaction in Uialertcontroller