Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
541 views
in Technique[技术] by (71.8m points)

ios - UIButton in cell in collection view not receiving touch up inside event

The following code expresses my problem: (It's self-contained in that you could create a Xcode project with an empty template, replace the contents of the main.m file, delete the AppDelegate.h/.m files and build it)

//
//  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]));
    }
}

Basically I'm creating a Springboard-type UI using collection views. My UICollectionViewCell subclass (Cell) has a button which lies partially outside the cell's contentView (i.e. its superview's) bounds.

The problem is that clicking on any part of the button outside of the contentView bounds (basically 3/4th of the button) doesn't invoke the button action. Only when clicking on the portion of the button that overlaps the contentView is the button's action method called.

I've even overridden -pointInside:withEvent: method in Cell so that touches in the button will be acknowledged. But that hasn't helped with the button clicking problem.

I'm guessing it might be something to do with how collectionView handles touches, but I don't know what. I know that UICollectionView is a UIScrollView subclass and I've actually tested that overriding -pointInside:withEvent: on a view (made subview to a scroll view) containing a partially overlapping button solves the button clicking problem, but it hasn't worked here.

Any help?

** Added: For the record, my current solution to the problem involves insetting a smaller subview to contentView which gives the cell its appearance. The delete button is added to the contentView such that its rect actually lies within the bounds of contentView but only partially overlaps the visible part of the cell (i.e. the inset subview). So I've got the effect I wanted, and the button is working properly. But I'm still curious about the problem with the original implementation above.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The problem appears to be with hitTest/pointInside. I'm guessing the cell is returning NO from pointInside if the touch is on the part of the button that is outside the cell and thus the button doesn't get hit tested. To fix this you have to override pointInside on your UICollectionViewCell subclass to take the button into account. You also need to override hitTest to return the button if the touch is inside the button. Here are example implementations assuming your button is in a property in the UICollectionViewCell subclass called deleteButton.

-(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];
}

Note that because hitTest and pointInside expect the point to be in the coordinate space of the receiver you have to remember to convert the point before calling those methods on the button.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...