UIScrollView touches vs subview touches

2019-02-07 02:05发布

Please can someone help sort a noob out? I've posted this problem on various forums and received no answers, while many searches for other answers have turned up stackOverflow, so I'm hoping this is the place.

I've got a BeachView.h (subclass of UIScrollView, picture of a sandy beach) covered with a random number of Stone.h (subclass of UIImageView, a random PNG of a stone, userInteractionEnabled = YES to accept touches).

If the user touches and moves on the beach, it should scroll. If the user taps a stone, it should call method "touchedStone". If the user taps the beach where there is no stone, it should call method "touchedBeach".

Now, I realize this sounds dead simple. Everyone and everything tells me that if there's something on a UIScrollView that accepts touches that it should pass control on to it. So when I touch and drag, it should scroll; but if I tap, and it's on a stone, it should ignore beach taps and accept stone taps, yes?

However, it seems that both views are accepting the tap and calling both touchedStone AND touchedBeach. Furthermore, the beach tap occurs first, so I can't even put in a "if touchedStone then don't run touchedBeach" type flag.

Here's some code. On BeachView.m


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
     if (self.decelerating) { didScroll = YES; }
        else { didScroll = NO; }

     UITouch *touch = [[event allTouches] anyObject];
     CGPoint touchLocation = [touch locationInView:touch.view];
     NSLog(@"touched beach = %@", [touch view]);
     lastTouch = touchLocation;
     [super touchesBegan:touches withEvent:event];
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
     didScroll = YES;
     [super touchesMoved:touches withEvent:event];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
     if (didScroll == NO && isPaused == NO) { 
          [self touchedBeach:YES location:lastTouch];
     }
     [super touchesEnded:touches withEvent:event];
}

On Stone.m


-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
     [parent stoneWasTouched]; // parent = ivar pointing from stone to beachview
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
     UITouch *touch = [[event allTouches] anyObject];
     CGPoint touchLocation = [touch locationInView:touch.view];
     NSLog(@"touched stone = %@", [touch view]);
     [parent touchedStone:YES location:touchLocation];
}

After a stone tap, My NSLog looks like this:


Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched Stone = <Stone: 0x1480c0>
ran touchedStone

So it's actually running both. What's even stranger is if I take the touchesBegan and touchesEnded out of Stone.m but leave userInteractionEnabled = YES, the beachView registers both touches itself, but returns the Stone as the view it touched (the second time).


Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched beach = <Stone: 0x1480c0>
ran touchedBeach

So PLEASE, I've been trying to sort this for days. How do I make it so a tapped stone calls only touchedStone and a tapped beach calls only touchedBeach? Where am I going wrong?

4条回答
叼着烟拽天下
2楼-- · 2019-02-07 02:14

This could be related to a bug in iOS7 please review my issue (bug report submitted)

UIScrollView subclass has changed behavior in iOS7

查看更多
\"骚年 ilove
3楼-- · 2019-02-07 02:18

Prior to iPhone OS 3.0, the UIScrollView's hitTest:withEvent: method always returns self so that it receives the UIEvent directly, only forwarding it to the appropriate subview if and when it determines it's not related to scrolling or zooming.

I couldn't really comment on iPhone OS 3.0 as it's under NDA, but check your "iPhone SDK Release notes for iPhone OS 3.0 beta 5" :)

If you need to target pre-3.0, you could override hitTest:withEvent: in BeachView and set a flag to ignore the next beach touch if the CGPoint is actually in a stone.

But have you tried simply moving your calls to [super touches*:withEvent:] from the end of your overridden methods to the start? This might cause the stone tap to occur first.

查看更多
时光不老,我们不散
4楼-- · 2019-02-07 02:18

Is true, iPhone SDK 3.0 and up, don't pass touches to -touchesBegan: and -touchesEnded: **UIScrollview**subclass methods anymore. You can use the touchesShouldBegin and touchesShouldCancelInContentView methods that is not the same.

If you really want to get this touches, have one hack that allow this.

In your subclass of UIScrollView override the hitTest method like this:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

  UIView *result = nil;
  for (UIView *child in self.subviews)
    if ([child pointInside:point withEvent:event])
      if ((result = [child hitTest:point withEvent:event]) != nil)
        break;

  return result;
}

This will pass to you subclass this touches, however you can't cancel the touches to UIScrollView super class.

查看更多
\"骚年 ilove
5楼-- · 2019-02-07 02:29

I had a similar problem with a simpleView and it is added to a scrollView , and whenever I touched the simpleView , the scrollView used to get the touch and instead of the simpleView , the scrollView moved . To avoid this , I disabled the srcolling of the scrollView when the user touched the simpleView and otherwise the scrolling is enabled .

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    UIView *result = [super hitTest:point withEvent:event] ;
    if (result == simpleView)
    {
        scrollView.scrollEnabled = NO ;
    }
    else
    {
        scrollView.scrollEnabled = YES ;
    }

    return result ;

}
查看更多
登录 后发表回答