5

我有一个视图层次结构,其中包含滚动视图上的较小视图。每个视图都可以有子视图,例如按钮等。

出于某种原因,视图上的按钮没有被点击;进一步探索表明,当滚动视图接收到 touchBegan 事件时,按钮没有。调用 hitTest:event: 消息显示按钮未返回,即使它在限制范围内。

我已经包含了一个日志输出,描述了滚动视图上的触摸位置、从 hitTest 返回的项目、如果我调用 locationInView 时的触摸位置:使用预期项目,以及预期项目的层次结构(打印帧)。从这个输出我可以推断该按钮应该被调用......

谁能解释一下?我错过了什么吗?

touched ({451, 309}) on <VCViewContainersView: 0x4b31ee0; frame = (0 0; 748 1024); transform = [0, 1, -1, 0, 0, 0]; autoresize = W+H; layer = <CALayer: 0x4b32130>> (location in expected item: {17, 7.5})
expected touched item is:
view: <UIButtonLabel: 0x482b920; frame = (32 5; 36 19); text = 'Click'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x4831370>>, layer transform: [1, 0, 0, 1, 0, 0]
 view: <UIRoundedRectButton: 0x482c100; frame = (50 50; 100 30); opaque = NO; layer = <CALayer: 0x482c450>>, layer transform: [1, 0, 0, 1, 0, 0]
  view: <UIImageView: 0x480f290; frame = (0 0; 320 255); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x480e840>>, layer transform: [1, 0, 0, 1, 0, 0]
   view: <VCViewContainer: 0x4b333c0; frame = (352 246.5; 320 471.75); layer = <CALayer: 0x4b33d50>>, layer transform: [1, 0, 0, 1, 0, 0]
    view: <UIScrollView: 0x4b32600; frame = (0 0; 1024 748); clipsToBounds = YES; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x4b32780>>, layer transform: [1, 0, 0, 1, 0, 0]
     view: <VCViewsContainerView: 0x4b31ee0; frame = (0 0; 748 1024); transform = [0, 1, -1, 0, 0, 0]; autoresize = W+H; layer = <CALayer: 0x4b32130>>, layer transform: [0, 1, -1, 0, 0, 0]
      view: <UIWindow: 0x4b1d590; frame = (0 0; 768 1024); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x4b1d6d0>>, layer transform: [1, 0, 0, 1, 0, 0]

更新:除了UIWindowVCViewsContainerView 之外,所有视图都是使用initWithFrame:或在按钮的情况下buttonWithType:以编程方式创建的。VCViewContainer 使用CGRectZero初始化,当UIImageView创建时,它的框架设置为图像的大小 + 底部标签的额外空间。

更新 2:当使用相同位置调用[self.layer hitTest:location]时,我得到了正确视图的图层!这里发生了什么...?

4

3 回答 3

5

hitTest:withEvent: 从窗口开始。每个视图在测试自身之前测试其子视图,以此类推,递归。但是,如果视图的 userInteractionEnabled 为 NO,它会从 hitTest:withEvent: 返回 nil,而不测试其子视图。这样的视图当然是经过命中测试的,但它会立即回复说它和它的任何子视图都不是命中视图。

您的 UIScrollView 将其 userInteractinonEnabled 设置为 NO。因此,当 VCViewContainersView 测试它的子视图 UIScrollView,并且 UIScrollView 返回 nil 因为它的 userInteractionEnabled 为 NO 时,VCViewContainersView 使用 pointInside:withEvent: 对自身,发现触摸在自身内部,并将自身作为命中视图返回(并且搜索结束)。这解释了你得到的结果。

通过图层进行命中测试时不会发生这种情况的原因是图层不可触摸并且对触摸规则一无所知,因此它们忽略了 userInteractionEnabled,这是一个视图功能,而不是图层功能。Layer hit-testing 是一种杂项,仅适用于当视图包含整个层层次结构(没有视图层次结构)并且您想要模拟特定层是可触摸的时。文档确实告诉您,层的 hitTest: 的逻辑与视图的 hitTest:withEvent: 的逻辑不同,尽管他们未能准确解释如何。

我不知道您为什么将滚动视图设置为不可触摸,但如果这对您很重要,您可以在 UIScrollView 子类中覆盖 hitTest:withEvent: 以便它测试其子视图但如果所有子视图都返回则返回 nil零。

于 2010-10-28T01:18:38.077 回答
1

You might want to try subclassing your UIScrollView to receive touches on objects within it.

By default your touches will be received by your UIScrollView, so if you want to receive touches on objects inside of it, you need to set that up explicitly.

Here's a working subclass to do just that. Thanks to the SO community for this, because I believe I originally found something like this here, just can't dig up the original post now.

Here's the .h:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface TouchScroller : UIScrollView 
{
}

@end

And the .m:

#import "TouchScroller.h"


@implementation TouchScroller

- (id)initWithFrame:(CGRect)frame 
{
return [super initWithFrame:frame];
}

- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event 
{   
// If not dragging, send event to next responder
if (!self.dragging) 
    [self.nextResponder touchesEnded: touches withEvent:event]; 
else
    [super touchesEnded: touches withEvent: event];
}

@end

That should do it.

于 2011-05-27T20:17:23.727 回答
0

如果我正确理解视图堆栈,那么您的按钮是某些 UIImageView 的子视图-它的userInteractionEnabled属性设置为 NO(默认情况下)-因此图像视图及其所有子视图都不会收到任何触摸事件。

将图像视图的userInteractionEnabled属性设置为YES必须解决问题

于 2010-10-22T10:36:12.523 回答