it-roy-ru.com

Выбор ячейки 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;
}

и состояние выбора работало как задумано.

Есть идеи?

45
Padin215

Ваше наблюдение верно. Такое поведение происходит из-за повторного использования клеток. Но вам не нужно ничего делать с 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
}
70
Anil Varghese

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

Это работает мне.

23
stefanB

UICollectionView изменился в iOS 10, добавив некоторые проблемы к решениям выше.

Вот хорошее руководство: https://littlebitesofcocoa.com/241-uicollectionview-cell-pre-fetching

Клетки теперь остаются на некоторое время после выхода за пределы экрана. Это означает, что иногда мы не можем получить ячейку в didDeselectItemAt indexPath для ее настройки. Затем он может отображаться на экране без обновления и не переработан. prepareForReuse не помогает в этом угловом случае.

Самое простое решение - отключить новую прокрутку, установив для isPrefetchingEnabled значение false. При этом управление отображением ячейки с помощью cellForItemAtdidSelectdidDeselect работает как раньше.

Тем не менее, если вы предпочитаете придерживаться нового режима плавной прокрутки, лучше использовать 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!
}

С помощью вышеизложенного вы управляете ячейкой, когда она выбрана, не выбрана на экране, переработана и просто повторно отображена.

19
Aleksander Niedziolko

Анил был на правильном пути (его решение выглядит так, как будто оно должно работать, я разработал это решение независимо от его). Я все еще использовал метод 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:

Это работает как шарм для меня.

10
Padin215

У меня было представление с горизонтальной прокруткой коллекции (я использую представление коллекции в Tableview), и я тоже столкнулся с проблемами повторного использования ячеек, когда я выбираю один элемент и прокручиваю вправо, некоторые другие ячейки в следующем видимом наборе выбираются автоматически. Попытка решить эту проблему с помощью любых пользовательских свойств ячейки, таких как «выбранный», выделенный и т.д., Не помогла мне, поэтому я пришел к следующему решению, и это сработало для меня.

Шаг 1:

Создайте переменную в collectionView для хранения выбранного индекса, здесь я использовал переменную уровня класса 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
anoop4real

В вашей пользовательской ячейке создайте публичный метод:

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

Также напишите переопределение метода ячейки -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;
}
2
landonandrey

Чтобы решить эту проблему, я должен был внести изменения в настраиваемую ячейку. У вас есть пользовательская ячейка с именем DataSetCell в своем классе, вы можете сделать следующее (код на Swift)

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

Это означает, что каждый раз, когда ячейка выбирается, отменяется выбор, инициализируется или вызывается из повторно используемой очереди, запускается этот код и вносятся изменения. Надеюсь, это поможет вам.

1
Eliezer Ferra

Проблема, с которой вы сталкиваетесь, связана с отсутствием вызова super.prepareForReuse().

Некоторые другие вышеупомянутые решения, предлагающие обновить пользовательский интерфейс ячейки из функций делегата, приводят к ошибочному дизайну, в котором логика поведения ячейки находится за пределами ее класса. Кроме того, это дополнительный код, который можно просто исправить, вызвав функцию super.prepareForReuse(). Например :

class myCell: UICollectionViewCell {

    // defined in interface builder
    @IBOutlet weak var viewSelection : UIView!

    override var isSelected: Bool {
        didSet {
            self.viewSelection.alpha = isSelected ? 1 : 0
        }
    }

    override func prepareForReuse() {
        // Do whatever you want here, but don't forget this :
        super.prepareForReuse()
        // You don't need to do `self.viewSelection.alpha = 0` here 
        // because `super.prepareForReuse()` will update the property `isSelected`

    }


    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        self.viewSelection.alpha = 0
    }

}

При таком дизайне вы можете даже оставить функции делегата collectionView:didSelectItemAt:/collectionView:didDeselectItemAt: пустыми, и процесс выбора будет полностью обработан и будет корректно работать с повторным использованием ячеек.

1
Pierre-Olivier Simonard

Только @ 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
 }
1
swiftBoy

Изменение свойства ячейки, такого как цвета фона ячейки, не должно быть сделано на самом UICollectionViewController, это должно быть сделано внутри вашего класса CollectionViewCell. Не используйте didSelect и didDeselect, просто используйте это:

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

вы можете просто установить selectedBackgroundView ячейки в backgroundColor = x.

Теперь каждый раз, когда вы нажимаете на ячейку, его выбранный режим будет автоматически изменяться и будет менять цвет фона на x.

0
evya

Благодаря вашему ответу @ 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
}
0
Ben