titlelabel color buttons ios uicollectionview uiresponder

color - menu ios



UIButton in cell en la vista de colección que no recibe un retoque dentro del evento (7)

El siguiente código expresa mi problema: (es independiente, ya que puede crear un proyecto Xcode con una plantilla vacía, reemplazar el contenido del archivo main.m, eliminar los archivos AppDelegate.h / .m y crearlo)

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

Básicamente estoy creando una IU tipo Springboard usando vistas de colección. Mi subclase UICollectionViewCell ( celda ) tiene un botón que se encuentra parcialmente fuera de los límites contentView de la celda (es decir, su supervisión).

El problema es que hacer clic en cualquier parte del botón fuera de los límites de ContentView (básicamente 3/4 del botón) no invoca la acción del botón. Solo al hacer clic en la parte del botón que se superpone a contentView se llama el método de acción del botón.

Incluso he invalidado el -pointInside:withEvent: en la celda para que se reconozcan los toques en el botón. Pero eso no ha ayudado con el botón haciendo clic en el problema.

Supongo que podría ser algo que ver con cómo CollectionView maneja los toques, pero no sé qué. Sé que UICollectionView es una subclase de UIScrollView y realmente he probado que anular -pointInside:withEvent: en una vista (subvista a una vista de desplazamiento) que contiene un botón parcialmente superpuesto resuelve el problema de hacer clic en el botón, pero no ha funcionado aquí .

¿Alguna ayuda?

** Agregado: para el registro, mi solución actual al problema consiste en insertar una subvista más pequeña en contentView que le da a la celda su apariencia. El botón de eliminación se agrega a contentView, de modo que su rect se encuentra realmente dentro de los límites de contentView, pero solo se superpone parcialmente a la parte visible de la celda (es decir, la subvista de recuadro). Así que tengo el efecto que quería y el botón funciona correctamente. Pero sigo sintiendo curiosidad por el problema con la implementación original anterior.


Como respuesta aceptada solicitada, debemos hacer un hitTest para recibir toques dentro de la celda. Aquí está el código Swift 4 para prueba de golpe:

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


El problema parece ser con hitTest / pointInside. Supongo que la celda está devolviendo el NO desde el punto interno si el toque está en la parte del botón que está fuera de la celda y, por lo tanto, el botón no se prueba de golpe. Para solucionar este problema, debe anular pointInside en su subclase UICollectionViewCell para tener en cuenta el botón. También debe anular hitTest para devolver el botón si el toque está dentro del botón. Aquí hay implementaciones de ejemplo que suponen que su botón está en una propiedad en la subclase UICollectionViewCell llamada 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]; }

Tenga en cuenta que debido a que hitTest y pointInside esperan que el punto esté en el espacio de coordenadas del receptor, debe acordarse de convertir el punto antes de llamar a esos métodos en el botón.


En Interface Builder, ¿ha configurado el objeto como UICollectionViewCell? Porque erróneamente una vez configuré una UIView y luego le asigné la clase correcta de UICollectionViewCell ... pero al hacer esto (botones, etiquetas, ecc.) No se agregan al contentView para que no respondan como lo harían ...

Entonces, recuerde en IB que tome el objeto UICollectionViewCell cuando dibuje la interfaz :)


Recibo con éxito toques en un botón creado de la siguiente manera en el archivo UICollectionViewCell.m subclasificado;

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

Agregué el botón en el código después de darme cuenta de que los botones que se agregaron en el Storyboard no funcionaron, no estoy seguro de si esto está arreglado en la última versión de Xcode.

Espero que ayude.


Tuve un problema similar al intentar colocar un botón de eliminación fuera de los límites de una celda de uicollectionview y no se ajustó para responder a los eventos de tap.

La forma en que lo resolví fue colocar un UITapGestureRecognizer en la colección y cuando un toque sucedió, se formó el siguiente código.

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


Veo dos conversiones rápidas de la respuesta original que no son exactamente conversiones rápidas. Así que solo quiero dar a Swift 4 conversión de la respuesta original para que todos los que quieran puedan usarla. Solo puede pegar el código en su subclassed UICollectionViewCell . Sólo asegúrese de cambiar closeButton con su propio botón.

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


Versión rápida:

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

eso es todo ... para todas las subvistas