51

选择单元格后,我想处理更改单元格外观。我认为委托方法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;
}

并且选择状态按预期工作。

有任何想法吗?

4

12 回答 12

75

你的观察是正确的。这种行为是由于单元格的重用而发生的。但是您不必对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
}
于 2013-04-02T07:26:14.793 回答
25

一旦您设置了单元格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;
}

这对我有用。

于 2013-10-03T11:58:06.903 回答
24

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

使用上述方法,您可以控制单元格被选中、在屏幕上取消选中、回收以及重新显示时。

于 2016-11-22T18:26:56.657 回答
11

Anil 走在了正确的轨道上(他的解决方案看起来应该可行,我独立于他开发了这个解决方案)。我仍然使用该prepareForReuse:方法将单元格设置selectedFALSE,然后在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:然后在and中处理单元格高亮和去高亮didSelectItemAtIndexPath:

这对我来说就像一个魅力。

于 2013-06-07T19:08:10.887 回答
6

我有一个水平滚动的集合视图(我在 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

于 2014-05-08T05:42:00.137 回答
4

我为解决这个问题所做的是在自定义单元格中进行更改。您在其类中有一个名为DataSetCell的自定义单元格,您可以执行以下操作(代码在 swift 中)

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

这样做的作用是每次选择、取消选择、初始化或从可重用队列中调用单元格时,该代码将运行并进行更改。希望这对您有所帮助。

于 2017-05-16T15:53:03.747 回答
2

在您的自定义单元格中创建公共方法:

- (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;
}
于 2014-07-11T09:34:06.877 回答
1

只有@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
 }
于 2016-04-26T08:48:09.730 回答
1

您遇到的问题来自缺少对super.prepareForReuse().

上面的一些其他解决方案,建议从委托的函数更新单元格的 UI,这导致了一个有缺陷的设计,其中单元格的行为逻辑在其类之外。此外,它是可以通过调用简单地修复的额外代码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:全部留空,选择过程将被完全处理,并在单元格回收时正常运行。

于 2018-09-06T08:44:18.700 回答
0

您可以将单元格的 selectedBackgroundView 设置为 backgroundColor=x。

现在,任何时候您点击单元格,他选择的模式都会自动更改,并且会根据背景颜色更改为 x。

于 2014-07-20T16:00:07.207 回答
0

感谢您的回答@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
}
于 2016-10-18T13:35:29.890 回答
0

更改单元格属性(例如单元格的背景颜色)不应该在 UICollectionViewController 本身上完成,它应该在 CollectionViewCell 类中完成。不要使用 didSelect 和 didDeselect,只要使用这个:

class MyCollectionViewCell: UICollectionViewCell 
{
     override var isSelected: Bool
     {
         didSet
         {
            // Your code
         }
     } 
}
于 2017-04-09T01:42:51.167 回答