Event Handling For iOS - How Hittest:Withevent: and Pointinside:Withevent: Are Related

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: of self
  • If the return is NO, hitTest:withEvent: returns nil. 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 first hitTest:withEvent: returns that object. the end of the story.
  • If no subview returns a non-nil object, the first hitTest:withEvent: returns self

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



Leave a reply



Submit