Выбор ячейки UICollectionView и повторное использование ячеек
При выборе ячейки я хочу обработать изменение внешнего вида ячейки. Я понял, что метод делегата collectionView:didSelectItemAtIndexPath:
и collectionView:didDeselectItemAtIndexPath:
- это то, где я должен редактировать ячейку.
-(void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
DatasetCell *datasetCell =
(DatasetCell *)[collectionView cellForItemAtIndexPath:indexPath];
[datasetCell replaceHeaderGradientWith:[UIColor skyBlueHeaderGradient]];
datasetCell.backgroundColor = [UIColor skyBlueColor];
}
и
-(void)collectionView:(UICollectionView *)collectionView
didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
DatasetCell *datasetCell =
(DatasetCell *)[collectionView cellForItemAtIndexPath:indexPath];
[datasetCell replaceHeaderGradientWith:[UIColor grayGradient]];
datasetCell.backgroundColor = [UIColor myDarkGrayColor];
}
Это работает отлично, за исключением случаев, когда ячейка повторно используется. Если я выбираю ячейку по индексу (0, 0), она меняет внешний вид, но когда я прокручиваю вниз, в выбранном состоянии есть еще одна ячейка.
Я считаю, что я должен использовать метод UICollectionViewCell
-(void)prepareForReuse
для подготовки ячейки для повторного использования (т.е. установить внешний вид ячейки в не выбранное состояние), но это дает мне трудности.
-(void)prepareForReuse {
if ( self.selected ) {
[self replaceHeaderGradientWith:[UIColor skyBlueHeaderGradient]];
self.backgroundColor = [UIColor skyBlueColor];
} else {
[self replaceHeaderGradientWith:[UIColor grayGradient]];
self.backgroundColor = [UIColor myDarkGrayColor];
}
}
Когда я прокручиваю назад вверх, ячейка с индексом (0, 0) находится в невыбранном состоянии.
Когда я просто использовал свойство cell.backgroundView, чтобы этого не произошло, выполните следующие действия:
-(void)prepareForReuse {
self.selected = FALSE;
}
и состояние выбора работало по назначению.
Любые идеи?
Ответы
Ответ 1
Ваше наблюдение верное. Такое поведение происходит из-за повторного использования клеток. Но вам не нужно ничего делать с prepareForReuse. Вместо этого сделайте свой чек в cellForItem и соответствующим образом настройте свойства. Что-то вроде..
- (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 redColor]; // 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 redColor]; // Default color
}
Ответ 2
Framework будет обрабатывать переключение просмотров для вас после настройки вашей ячейки backgroundView
и selectedBackgroundView
, см. пример из Управление визуальное состояние для выбора и основных параметров:
UIView* backgroundView = [[UIView alloc] initWithFrame:self.bounds];
backgroundView.backgroundColor = [UIColor redColor];
self.backgroundView = backgroundView;
UIView* selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
selectedBGView.backgroundColor = [UIColor whiteColor];
self.selectedBackgroundView = selectedBGView;
вам нужен только ваш класс, который реализует выделение и выделение ячеек UICollectionViewDelegate
, таких как:
- (BOOL)collectionView:(UICollectionView *)collectionView
shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (BOOL)collectionView:(UICollectionView *)collectionView
shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
{
return YES;
}
Это работает.
Ответ 3
UICollectionView изменилось в iOS 10, введя некоторые проблемы для решений выше.
Вот хорошее руководство:
https://littlebitesofcocoa.com/241-uicollectionview-cell-pre-fetching
Клетки теперь остаются немного после выхода за пределы экрана. Это означает, что иногда мы не можем получить ячейку в didDeselectItemAt indexPath
, чтобы ее настроить. Затем он может отображаться на экране без обновления и без повторного использования. prepareForReuse
не помогает этому углу.
Самое простое решение - отключить новую прокрутку, установив isPrefetchingEnabled
в значение false. При этом управление отображением ячейки с помощью cellForItemAt
didSelect
didDeselect
работает так, как раньше.
Однако, если вы предпочитаете сохранять новое плавное поведение прокрутки, лучше использовать 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!
}
С приведенным выше вы управляете ячейкой, когда она выбрана, не выбрана на экране, переработана и просто повторно отображена.
Ответ 4
Анил был на правильном пути (его решение похоже, что он должен работать, я разработал это решение независимо от его). Я все еще использовал метод prepareForReuse:
, чтобы установить ячейку selected
в FALSE
, затем в тесте cellForItemAtIndexPath
, чтобы проверить, находится ли индекс ячейки в `collectionView.indexPathsForSelectedItems ', если это так, выделите его.
В пользовательской ячейке:
-(void)prepareForReuse {
self.selected = FALSE;
}
В cellForItemAtIndexPath:
для обработки ячеек повторного использования подсветки и де-подсветки:
if ([collectionView.indexPathsForSelectedItems containsObject:indexPath]) {
[collectionView selectItemAtIndexPath:indexPath animated:FALSE scrollPosition:UICollectionViewScrollPositionNone];
// Select Cell
}
else {
// Set cell to non-highlight
}
И затем обработайте выделение и дефокусировку ячейки в didDeselectItemAtIndexPath:
и didSelectItemAtIndexPath:
Это работает как прелесть для меня.
Ответ 5
У меня было представление коллекции с горизонтальной прокруткой (я использую представление коллекции в Tableview), и я тоже сталкивался с проблемами с повторным использованием элементов, когда я выбираю один элемент и прокручиваю вправо, некоторые другие ячейки в следующем видимом наборе автоматически выбираются. Попытка решить эту проблему с помощью любых пользовательских свойств ячеек, таких как "выбранные", выделенные и т.д. Не помогла мне, поэтому я придумал решение ниже, и это сработало для меня.
Шаг 1:
Создайте переменную в коллекцииView для хранения выбранного индекса, здесь я использовал переменную уровня класса с именем 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 redColor] 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 redColor] 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
Ответ 6
В вашей пользовательской ячейке создайте открытый метод:
- (void)showSelection:(BOOL)selection
{
self.contentView.backgroundColor = selection ? [UIColor blueColor] : [UIColor white];
}
Также напишите redefenition -prepareForReuse метод ячейки:
- (void)prepareForReuse
{
[self showSelection:NO];
[super prepareForReuse];
}
И в вашем ViewController вы должны иметь переменную _selectedIndexPath, которая определена в -didSelectItemAtIndexPath и сбрасывается в -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;
}
Ответ 7
Только решение @stefanB работало для меня на iOS 9.3
Здесь я должен изменить для 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.redColor()
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
}
Ответ 8
вы можете просто установить selectedBackgroundView ячейки как backgroundColor = x.
Теперь, когда вы нажмете на ячейку, его выбранный режим изменится автоматически и перейдет к цвету фона, чтобы перейти на x.
Ответ 9
Благодаря вашему ответу @RDC.
Следующие коды работают с 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
}
Ответ 10
Изменение свойства ячейки, такого как цвета фона ячейки, не должно выполняться на самом UICollectionViewController, это должно быть сделано внутри класса CollectionViewCell. Не используйте didSelect и didDeselect, просто используйте это:
class MyCollectionViewCell: UICollectionViewCell
{
override var isSelected: Bool
{
didSet
{
// Your code
}
}
}
Ответ 11
Что я сделал, чтобы решить это, нужно внести изменения в настраиваемую ячейку. У вас есть пользовательская ячейка, называемая DataSetCell в своем классе, которую вы можете сделать (код в быстром)
override var isSelected: Bool {
didSet {
if isSelected {
changeStuff
} else {
changeOtherStuff
}
}
}
Это означает, что каждый раз, когда ячейка выбирается, отменяется, инициализируется или вызывается из очереди многократного использования, этот код запускается и изменения будут сделаны. Надеюсь, это поможет вам.