集合视图中的单元格中的UIButton未在事件内部接收修饰

Aky*_*Aky 23 uiresponder ios uicollectionview

下面的代码表达了我的问题:(它是自包含的,你可以创建一个带有空模板的Xcode项目,替换main.m文件的内容,删除AppDelegate.h/.m文件并构建它)

//
//  main.m
//  CollectionViewProblem
//


#import <UIKit/UIKit.h>

@interface Cell : UICollectionViewCell

@property (nonatomic, strong) UIButton *button;
@property (nonatomic, strong) UILabel *label;

@end

@implementation Cell
 - (id)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
    {
        self.label = [[UILabel alloc] initWithFrame:self.bounds];
        self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        self.label.backgroundColor = [UIColor greenColor];
        self.label.textAlignment = NSTextAlignmentCenter;

        self.button = [UIButton buttonWithType:UIButtonTypeInfoLight]; 
        self.button.frame = CGRectMake(-frame.size.width/4, -frame.size.width/4, frame.size.width/2, frame.size.width/2);
        self.button.backgroundColor = [UIColor redColor];
        [self.button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
        [self.contentView addSubview:self.label];
        [self.contentView addSubview:self.button];
    }
    return self;
}


// Overriding this because the button's rect is partially outside the parent-view's bounds:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if ([super pointInside:point withEvent:event])
    {
        NSLog(@"inside cell");
        return YES;
    }
    if ([self.button
         pointInside:[self convertPoint:point
                                 toView:self.button] withEvent:nil])
    {
        NSLog(@"inside button");
        return YES;
    }

    return NO;
}


- (void)buttonClicked:(UIButton *)sender
{
    NSLog(@"button clicked!");
}
@end

@interface ViewController : UICollectionViewController

@end

@implementation ViewController

// (1a) viewdidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.collectionView registerClass:[Cell class] forCellWithReuseIdentifier:@"ID"];
}

// collection view data source methods ////////////////////////////////////

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 100;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ID" forIndexPath:indexPath];
    cell.label.text = [NSString stringWithFormat:@"%d", indexPath.row];
    return cell;
}
///////////////////////////////////////////////////////////////////////////

// collection view delegate methods ////////////////////////////////////////

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"cell #%d was selected", indexPath.row);
}
////////////////////////////////////////////////////////////////////////////
@end


@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    ViewController *vc = [[ViewController alloc] initWithCollectionViewLayout:layout];


    layout.itemSize = CGSizeMake(128, 128);
    layout.minimumInteritemSpacing = 64;
    layout.minimumLineSpacing = 64;
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    layout.sectionInset = UIEdgeInsetsMake(32, 32, 32, 32);


    self.window.rootViewController = vc;

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end


int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上我正在使用集合视图创建一个Springboard类型的UI.我的UICollectionViewCell子类(Cell)有一个按钮,它位于单元格的contentView(即其superview的)边界之外.

问题是单击contentView边界之外的按钮的任何部分(基本上是按钮的3/4)不会调用按钮操作.仅当单击与contentView重叠的按钮部分时,才会调用按钮的操作方法.

我甚至-pointInside:withEvent:Cell中重写了方法,以便确认按钮中的触摸.但这对按钮点击问题没有帮助.

我猜它可能与collectionView如何处理触摸有关,但我不知道是什么.我知道UICollectionView是一个UIScrollView子类,我实际上已经测试了覆盖-pointInside:withEvent:视图(包含一个滚动视图的子视图),包含一个部分重叠的按钮解决了按钮点击问题,但它在这里没有用.

有帮助吗?

**补充:为了记录,我目前解决问题的方法是将一个较小的子视图嵌入到contentView中,从而为该单元提供外观.删除按钮被添加到contentView,使其rect实际上位于contentView的边界内,但仅部分重叠单元格的可见部分(即插入子视图).所以我得到了我想要的效果,按钮工作正常.但我仍然对上面原始实现的问题感到好奇.

hon*_*nus 25

问题似乎是hitTest/pointInside.我猜测单元格是从pointInside返回NO,如果触摸位于单元格外部的按钮部分,因此按钮不会受到测试.要解决此问题,您必须覆盖UICollectionViewCell子类上的pointInside以将该按钮考虑在内.如果触摸在按钮内,您还需要覆盖hitTest以返回按钮.下面是示例实现,假设您的按钮位于名为deleteButton的UICollectionViewCell子类中的属性中.

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [self.deleteButton hitTest:[self.deleteButton convertPoint:point fromView:self] withEvent:event];
    if (view == nil) {
        view = [super hitTest:point withEvent:event];
    }
    return view;
}

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if ([super pointInside:point withEvent:event]) {
        return YES;
    }
    //Check to see if it is within the delete button
    return !self.deleteButton.hidden && [self.deleteButton pointInside:[self.deleteButton convertPoint:point fromView:self] withEvent:event];
}
Run Code Online (Sandbox Code Playgroud)

请注意,因为hitTest和pointInside期望该点位于接收器的坐标空间中,所以您必须记住在按钮上调用这些方法之前转换该点.


jer*_*gdm 13

在Interface Builder中,您是否将对象设置为UICollectionViewCell?因为错误的一次我设置了一个UIView并在为它分配了正确的UICollectionViewCell类之后......但是这些东西(按钮,标签,ecc.)没有被添加到contentView中,因此它们不会像它们那样响应......

所以,在IB中提醒在绘制界面时采用UICollectionViewCell对象:)


Kva*_*ant 5

Swift版本:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    //From higher z- order to lower except base view;

    for (var i = subviews.count-2; i >= 0 ; i--){
        let newPoint = subviews[i].convertPoint(point, fromView: self)
        let view = subviews[i].hitTest(newPoint, withEvent: event)
        if view != nil{
            return view
        }
    }

    return super.hitTest(point, withEvent: event)

}
Run Code Online (Sandbox Code Playgroud)

这就是...对于所有子视图


Dan*_*rdh 3

我成功接收对子类 UICollectionViewCell.m 文件中创建的按钮的触摸;

- (id)initWithCoder:(NSCoder *)aDecoder
    {
    self = [super initWithCoder:aDecoder];
    if (self)
    {

    // Create button

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(0, 0, 100, 100); // position in the parent view and set the size of the button
    [button setTitle:@"Title" forState:UIControlStateNormal];
    [button setImage:[UIImage imageNamed:@"animage.png"] forState:UIControlStateNormal];
    [button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchUpInside];

    // add to contentView
    [self.contentView addSubview:button];
    }
    return self;
}
Run Code Online (Sandbox Code Playgroud)

在意识到 Storyboard 中添加的按钮不起作用后,我在代码中添加了该按钮,不确定这是否在最新的 Xcode 中得到修复。

希望有帮助。