UIButton dans la cellule de la collection Voir ne pas recevoir de retouche à l'intérieur de l'événement

Le code suivant exprime mon problème: (Il est autonome en ce que vous pouvez créer un projet Xcode avec un modèle vide, remplacer le contenu de la main.m fichier, supprimer l'AppDelegate.h./m de fichiers et de le construire)

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

en gros, je crée une interface utilisateur de type tremplin en utilisant des vues de collecte. Mon Uicollection Viewcell sous-classe (Cellule) dispose d'un bouton qui se trouve partiellement à l'extérieur de la cellule contentView (c'est à dire son superview) limites.

le problème est que cliquer sur n'importe quelle partie du bouton en dehors de la contentView limites (en gros 3/4 le bouton) n'a pas d'invoquer le bouton d'action. Seulement en cliquant sur la partie du bouton qui chevauche le contentView est la méthode d'action du bouton appelée.

j'ai même remplacé -pointInside:withEvent: méthode Cellule pour que les touches dans le bouton soient reconnues. Mais cela n'a pas aidé avec le bouton problème de clic.

je suppose que cela pourrait être quelque chose à voir avec comment collectionView poignées touche, mais je ne sais pas quoi. Je sais que UICollectionView est un UIScrollView sous-classe, et en fait, j'ai testé que primordial -pointInside:withEvent: sur une vue (Rendu subview à une vue scroll) contenant un bouton partiellement se chevauchant résout le problème de clic de bouton, mais il n'a pas fonctionné ici.

Tout de l'aide?

** Ajout de l': Pour mémoire, ma solution actuelle au problème consiste à insérer un sous-vue plus petit vers contentView qui donne à la cellule son apparence. Le bouton supprimer est ajouté à l' contentView tel que son rect se trouve en fait dans les limites de contentView mais ne chevauche que partiellement la partie visible de la cellule (i.e. la sous-fenêtre inset). J'ai donc eu l'effet que je voulais, et le bouton fonctionne correctement. Mais je suis toujours curieux sur le problème de la mise en œuvre initiale ci-dessus.

20
demandé sur Zev Eisenberg 2012-11-04 15:44:36

7 réponses

le problème semble être avec hitTest / pointtinside. Je devine que la cellule ne retourne pas de pointInside si le toucher est sur la partie du bouton qui est à l'extérieur de la cellule et donc le bouton ne se fait pas testé. Pour corriger cela, vous devez modifier le pointInside de votre sous-classe UICollectionViewCell pour tenir compte du bouton. Vous devez également annuler hitTest pour retourner le bouton si la touche est à l'intérieur du bouton. Voici des exemples d'implémentation en supposant que votre bouton est dans un propriété de la sous-classe UICollectionViewCell appelée 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];
}

notez que parce que hitTest et pointInside s'attendent à ce que le point soit dans l'espace de coordonnées du récepteur, vous devez vous rappeler de convertir le point avant d'appeler ces méthodes sur le bouton.

23
répondu honus 2014-08-22 02:35:58

Dans Interface Builder avez-vous l'objet que UICollectionViewCell? Parce qu'une fois par erreur j'ai mis un UIView et après lui avoir assigné la classe uicollectioncewcell correcte...mais faire ces choses (boutons, étiquettes, ecc.) ne sont pas ajoutés au contentView, de sorte qu'ils ne répondent pas comme ils le feraient...

Donc, rappeler à l'IB de prendre le UICollectionViewCell Objet lors de l'élaboration de l'interface :)

11
répondu jerrygdm 2013-01-03 19:17:44

version 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)

}

c'est tout ... pour tous les sous-vues

5
répondu Kvant 2016-11-22 14:22:20

je reçois avec succès des touches à un bouton créé comme suit dans la sous-classe UICollectionViewCell.m fichier;

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

j'ai ajouté le bouton en code après avoir réalisé que les boutons ajoutés dans Storyboard ne fonctionnaient pas, pas sûr que ce soit corrigé dans le dernier Xcode.

J'espère que ça aidera.

4
répondu Daniel Nordh 2012-11-04 20:52:40

j'ai eu un problème similaire en essayant de placer un bouton de suppression en dehors des limites d'une cellule uicollectionview et il n'a pas Joint pour répondre aux événements de robinet.

la façon dont je l'ai résolu était de placer un UITapGestureRecognizer sur la collection et quand un robinet happend préformer le code suivant

//this works also on taps outside the cell bouns, im guessing by getting the closest cell to the point of click.

NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:[tapRecognizer locationInView:self.collectionView]]; 

if(tappedCellPath) {
    UICollectionViewCell *tappedCell = [self.collectionView cellForItemAtIndexPath:tappedCellPath];
    CGPoint tapInCellPoint = [tapRecognizer locationInView:tappedCell];
    //if the tap was outside of the cell bounds then its in negative values and it means the delete button was tapped
    if (tapInCellPoint.x < 0) [self deleteCell:tappedCell]; 
}
1
répondu Boaz Saragossi 2013-04-28 09:26:02

comme réponse acceptée demandée, nous devrions faire un hitTest afin de recevoir des touches à l'intérieur de la cellule. Voici le code Swift 4 pour hit test:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
  for i in (0..<subviews.count-1).reversed() {
    let newPoint = subviews[i].convert(point, from: self)
    if let view = subviews[i].hitTest(newPoint, with: event) {
        return view
    }
  }
  return super.hitTest(point, with: event)
}
1
répondu cem olcay 2018-02-25 16:45:17

je vois deux conversions swift de la réponse originale qui ne sont pas exactement des conversions swift. Donc, je veux juste donner l' Swift 4 conversion de la réponse originale pour que tous ceux qui le veulent puissent l'utiliser. Vous pouvez simplement coller le code dans votre subclassed UICollectionViewCell. Assurez-vous simplement que vous changez closeButton avec votre propre touche.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    var view = closeButton.hitTest(closeButton.convert(point, from: self), with: event)
    if view == nil {
        view = super.hitTest(point, with: event)
    }

    return view
}

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    if super.point(inside: point, with: event) {
        return true
    }

    return !closeButton.isHidden && closeButton.point(inside: closeButton.convert(point, from: self), with: event)
}
0
répondu NoSixties 2018-04-16 12:49:46