Selección de células UICollectionView y reutilización de células

Tras la selección de la celda, quiero manejar la modificación del aspecto de la celda. Pensé en el método delegate collectionView:didSelectItemAtIndexPath: & collectionView:didDeselectItemAtIndexPath: es donde debería editar la celda.

 -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { DatasetCell *datasetCell = (DatasetCell *)[collectionView cellForItemAtIndexPath:indexPath]; [datasetCell replaceHeaderGradientWith:[UIColor skyBlueHeaderGradient]]; datasetCell.backgroundColor = [UIColor skyBlueColor]; } 

y

 -(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { DatasetCell *datasetCell = (DatasetCell *)[collectionView cellForItemAtIndexPath:indexPath]; [datasetCell replaceHeaderGradientWith:[UIColor grayGradient]]; datasetCell.backgroundColor = [UIColor myDarkGrayColor]; } 

Esto funciona bien, excepto cuando la célula se reutiliza. Si selecciono la celda en el índice (0, 0), cambia la apariencia pero cuando me desploop hacia abajo, hay otra celda en el estado seleccionado.

Creo que debería usar el método UICollectionViewCell -(void)prepareForReuse para preparar la celda para su reutilización (es decir, establecer la apariencia de la celda en un estado no seleccionado) pero me está dando dificultades.

 -(void)prepareForReuse { if ( self.selected ) { [self replaceHeaderGradientWith:[UIColor skyBlueHeaderGradient]]; self.backgroundColor = [UIColor skyBlueColor]; } else { [self replaceHeaderGradientWith:[UIColor grayGradient]]; self.backgroundColor = [UIColor myDarkGrayColor]; } } 

Cuando vuelvo a la parte superior, la celda en el índice (0, 0) está en el estado deseleccionado.

Cuando acabo de usar la propiedad cell.backgroundView, para evitar que esto ocurra fue a:

 -(void)prepareForReuse { self.selected = FALSE; } 

y el estado de selección funcionó según lo previsto.

¿Algunas ideas?

Tu observación es correcta. Este comportamiento está sucediendo debido a la reutilización de las celdas. Pero no tienes que hacer nada con PrepareReuse . En su lugar, realice su comprobación en cellForItem y configure las properties en consecuencia. Algo como..

  - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cvCell" forIndexPath:indexPath]; if (cell.selected) { cell.backgroundColor = [UIColor blueColor]; // highlight selection } else { cell.backgroundColor = [UIColor networkingColor]; // Default color } return cell; } -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath]; datasetCell.backgroundColor = [UIColor blueColor]; // highlight selection } -(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath]; datasetCell.backgroundColor = [UIColor networkingColor]; // Default color } 

Framework se encargará de cambiar las vistas para usted una vez que haya configurado backgroundView su celda y selectedBackgroundView , consulte el ejemplo de Gestión del estado visual para Selections and Highlights :

 UIView* backgroundView = [[UIView alloc] initWithFrame:self.bounds]; backgroundView.backgroundColor = [UIColor networkingColor]; self.backgroundView = backgroundView; UIView* selectedBGView = [[UIView alloc] initWithFrame:self.bounds]; selectedBGView.backgroundColor = [UIColor whiteColor]; self.selectedBackgroundView = selectedBGView; 

solo necesita en su class que implementa UICollectionViewDelegate habilitar las celdas para resaltar y seleccionar así:

 - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath; { return YES; } 

Esto me funciona.

UICollectionView ha cambiado en iOS 10 introduciendo algunos problemas a las soluciones anteriores.

Aquí hay una buena guía: https://littlebitesofcocoa.com/241-uicollectionview-cell-pre-fetching

Las celdas ahora se quedan un poco después de salir de la pantalla. Lo que significa que a veces es posible que no podamos get una celda en didDeselectItemAt indexPath para ajustarla. Puede aparecer en la pantalla sin actualizar y sin reciclar. prepareForReuse no ayuda en este caso de esquina.

La solución más fácil es deshabilitar el nuevo desplazamiento configurando isPrefetchingEnabled en false. Con esto, administrar la pantalla de la celda con cellForItemAt didSelect didDeselect funciona como solía hacerlo.

Sin embargo, si prefiere mantener el nuevo comportamiento de desplazamiento suave, es mejor usar willDisplay :

 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { let customCell = cell as! CustomCell if customCell.isSelected { customCell.select() } else { customCell.unselect() } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell //Don't even need to set selection-specific things here as recycled cells will also go through willDisplay return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let cell = collectionView.cellForItem(at: indexPath) as? CustomCell cell?.select() } func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { let cell = collectionView.cellForItem(at: indexPath) as? CustomCell cell?.unselect() // <----- this can be null here, and the cell can still come back on screen! } 

Con lo anterior, usted controla la celda cuando está seleccionada, no se selecciona en la pantalla, se recicla y simplemente se vuelve a mostrar.

Anil estaba en el path correcto (su solución parece funcionar, desarrollé esta solución independientemente de la suya). Todavía usé el método prepareForReuse: para establecer la celda selected en FALSE , luego en cellForItemAtIndexPath , cellForItemAtIndexPath si el índice de la celda está en `collectionView.indexPathsForSelectedItems ', de ser así, resáltalo.

En la celda personalizada:

 -(void)prepareForReuse { self.selected = FALSE; } 

En cellForItemAtIndexPath: para manejar las celdas de reutilización y resalte dehighlighting:

 if ([collectionView.indexPathsForSelectedItems containsObject:indexPath]) { [collectionView selectItemAtIndexPath:indexPath animated:FALSE scrollPosition:UICollectionViewScrollPositionNone]; // Select Cell } else { // Set cell to non-highlight } 

Y luego manejar resaltado de celda y deshacer aclaraciones en didDeselectItemAtIndexPath: y didSelectItemAtIndexPath:

Esto funciona como un encanto para mí.

Tenía una vista de colección de desplazamiento horizontal (uso la vista de colección en Tableview) y también me enfrenté a problemas con la reutilización celular, cada vez que selecciono un elemento y me desploop hacia la derecha, otras celdas en el siguiente set visible se seleccionan automáticamente. Tratando de resolver esto usando cualquier propiedad de celda personalizada como "seleccionado", resaltado, etc. no me ayudó, así que se me ocurrió la siguiente solución y esto funcionó para mí.

Paso 1:

Cree una variable en la colecciónView para almacenar el índice seleccionado, aquí he usado una variable de nivel de class llamada selectedIndex

 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MyCVCell *cell = (MyCVCell*)[collectionView dequeueReusableCellWithReuseIdentifier:@"MyCVCell" forIndexPath:indexPath]; // When scrolling happens, set the selection status only if the index matches the selected Index if (selectedIndex == indexPath.row) { cell.layer.borderWidth = 1.0; cell.layer.borderColor = [[UIColor networkingColor] CGColor]; } else { // Turn off the selection cell.layer.borderWidth = 0.0; } return cell; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { MyCVCell *cell = (MyCVCell *)[collectionView cellForItemAtIndexPath:indexPath]; // Set the index once user taps on a cell selectedIndex = indexPath.row; // Set the selection here so that selection of cell is shown to ur user immediately cell.layer.borderWidth = 1.0; cell.layer.borderColor = [[UIColor networkingColor] CGColor]; [cell setNeedsDisplay]; } - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { MyCVCell *cell = (MyCVCell *)[collectionView cellForItemAtIndexPath:indexPath]; // Set the index to an invalid value so that the cells get deselected selectedIndex = -1; cell.layer.borderWidth = 0.0; [cell setNeedsDisplay]; } 

-anoop

En su celda personalizada, cree un método público:

 - (void)showSelection:(BOOL)selection { self.contentView.backgroundColor = selection ? [UIColor blueColor] : [UIColor white]; } 

También escriba networkingefinición de -prepareForReuse método de celda:

 - (void)prepareForReuse { [self showSelection:NO]; [super prepareForReuse]; } 

Y en su ViewController debería tener _selectedIndexPath variable, que se definió en -didSelectItemAtIndexPath y anulado en -didDeselectItemAtIndexPath

 NSIndexPath *_selectedIndexPath; - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellIdentifier = @"Cell"; UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; if (_selectedIndexPath) { [cell showSelection:[indexPath isEqual:_selectedIndexPath]]; } } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; [cell showSelection:![indexPath isEqual:_selectedIndexPath]];// on/off selection _selectedIndexPath = [indexPath isEqual:_selectedIndexPath] ? nil : indexPath; } - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; [cell showSelection:NO]; _selectedIndexPath = nil; } 

Solo la solución @stefanB funcionó para mí en iOS 9.3

Aquí lo que tengo que cambiar para Swift 2

 func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { //prepare your cell here.. //Add background view for normal cell let backgroundView: UIView = UIView(frame: cell!.bounds) backgroundView.backgroundColor = UIColor.lightGrayColor() cell!.backgroundView = backgroundView //Add background view for selected cell let selectedBGView: UIView = UIView(frame: cell!.bounds) selectedBGView.backgroundColor = UIColor.networkingColor() cell!.selectedBackgroundView = selectedBGView return cell! } func collectionView(collectionView: UICollectionView, shouldHighlightItemAtIndexPath indexPath: NSIndexPath) -> Bool { return true } func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool { return true } 

puede simplemente establecer elViewBackgroundView seleccionado de la celda para backgroundColor = x.

Ahora, cada vez que toque en la celda, su modo seleccionado cambiará automáticamente y coincidirá con el color de background para cambiar a x.

Gracias a tu respuesta @ RDC .

Los siguientes códigos funcionan con Swift 3

 // MARK: - UICollectionViewDataSource protocol func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { //prepare your cell here.. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCell cell.myLabel.text = "my text" //Add background view for normal cell let backgroundView: UIView = UIView(frame: cell.bounds) backgroundView.backgroundColor = UIColor.lightGray cell.backgroundView = backgroundView //Add background view for selected cell let selectedBGView: UIView = UIView(frame: cell.bounds) selectedBGView.backgroundColor = UIColor.green cell.selectedBackgroundView = selectedBGView return cell } // MARK: - UICollectionViewDelegate protocol func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { return true } func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { return true } 

Cambiar la propiedad de la celda, como los colors de background de la celda, no debería hacerse en el UICollectionViewController mismo, sino que debe hacerse dentro de la class CollectionViewCell. No use didSelect y didDeselect, solo use esto:

 class MyCollectionViewCell: UICollectionViewCell { override var isSelected: Bool { didSet { // Your code } } } 

Lo que hice para resolver esto fue hacer los cambios en la celda personalizada. Tiene una celda personalizada llamada DataSetCell en su class, puede hacer lo siguiente (el código está en swift)

 override var isSelected: Bool { didSet { if isSelected { changeStuff } else { changeOtherStuff } } } 

Lo que esto hace es que cada vez que se selecciona, deselecciona, inicializa o recibe una llamada de la queue reutilizable, ese código se ejecutará y se realizarán los cambios. Espero que esto te ayude.