简体   繁体   English

UICollectionView单元选择和单元重用

[英]UICollectionView cell selection and cell reuse

Upon cell selection, I want to handle changing the cell appearance. 选择单元格后,我想处理更改单元格外观。 I figured the delegate method collectionView:didSelectItemAtIndexPath: & collectionView:didDeselectItemAtIndexPath: is where I should edit the cell. 我想到了委托方法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];
}

and

-(void)collectionView:(UICollectionView *)collectionView 
       didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {

    DatasetCell *datasetCell = 
      (DatasetCell *)[collectionView cellForItemAtIndexPath:indexPath];

    [datasetCell replaceHeaderGradientWith:[UIColor grayGradient]];
    datasetCell.backgroundColor = [UIColor myDarkGrayColor];
}

This works fine, except when the cell gets reused. 这种方法很好,除非重用单元格。 If I select cell at index (0, 0), it changes the appearance but when I scroll down, there is another cell in the selected state. 如果我在索引(0,0)处选择单元格,它会更改外观,但是当我向下滚动时,会有另一个单元格处于选定状态。

I believe I should use the UICollectionViewCell method -(void)prepareForReuse to prep the cell for resuse (ie, set the cell appearance to non selected state) but its giving me difficulties. 我相信我应该使用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];
    }
}

When I scroll back to the top, the cell at index (0, 0) is in the deselected state. 当我向后滚动到顶部时,索引(0,0)处的单元格处于取消选择状态。

When I just used the cell.backgroundView property, to prevent this from happening was to: 当我刚刚使用cell.backgroundView属性时,为了防止这种情况发生在:

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

and the selection state worked as intended. 并且选择状态按预期工作。

Any ideas? 有任何想法吗?

Your observation is correct. 你的观察是正确的。 This behavior is happening due to the reuse of cells. 由于重用单元格,这种情况正在发生。 But you dont have to do any thing with the prepareForReuse . 但是你不必对prepareForReuse做任何事情。 Instead do your check in cellForItem and set the properties accordingly. 而是检查cellForItem并相应地设置属性。 Some thing like.. 就像是..

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

Framework will handle switching the views for you once you setup your cell's backgroundView and selectedBackgroundView , see example from Managing the Visual State for Selections and Highlights : 设置单元格的backgroundViewselectedBackgroundView框架将处理为您切换视图 ,请参阅管理选择和突出显示的可视状态示例:

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;

you only need in your class that implements UICollectionViewDelegate enable cells to be highlighted and selected like this: 你只需要在你的类中实现UICollectionViewDelegate启用单元格突出显示并选择如下:

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

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

This works me. 这对我有用。

UICollectionView has changed in iOS 10 introducing some problems to solutions above. UICollectionView在iOS 10中发生了变化,为上述解决方案带来了一些问题。

Here is a good guide: https://littlebitesofcocoa.com/241-uicollectionview-cell-pre-fetching 这是一个很好的指南: https//littlebitesofcocoa.com/241-uicollectionview-cell-pre-fetching

Cells now stay around for a bit after going off-screen. 离开屏幕后,细胞现在可以保持一段时间。 Which means that sometimes we might not be able to get hold of a cell in didDeselectItemAt indexPath in order to adjust it. 这意味着有时我们可能无法在didDeselectItemAt indexPath中获取一个单元格来调整它。 It can then show up on screen un-updated and un-recycled. 然后它可以在屏幕上显示未更新和未回收。 prepareForReuse does not help this corner case. prepareForReuse没有帮助这个角落的情况。

The easiest solution is disabling the new scrolling by setting isPrefetchingEnabled to false. 最简单的解决方案是通过将isPrefetchingEnabled设置为false来禁用新滚动。 With this, managing the cell's display with cellForItemAt didSelect didDeselect works as it used to. 有了这个,使用cellForItemAt管理单元格的显示didSelect didDeselect就像didDeselect工作。

However, if you'd rather keep the new smooth scrolling behaviour it's better to use willDisplay : 但是,如果你想保持新的平滑滚动行为,最好使用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!
}

With the above you control the cell when it's selected, unselected on-screen, recycled, and just re-displayed. 通过上述操作,您可以在选择单元格时控制单元格,在屏幕上取消选择,回收并重新显示。

Anil was on the right track (his solution looks like it should work, I developed this solution independently of his). Anil走在正确的轨道上(他的解决方案看起来应该可行,我开发了独立于他的解决方案)。 I still used the prepareForReuse: method to set the cell's selected to FALSE , then in the cellForItemAtIndexPath I check to see if the cell's index is in `collectionView.indexPathsForSelectedItems', if so, highlight it. 我仍然使用prepareForReuse:方法将selected的单元格设置为FALSE ,然后在cellForItemAtIndexPath检查单元格的索引是否在`collectionView.indexPathsForSelectedItems'中,如果是,则突出显示它。

In the custom cell: 在自定义单元格中:

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

In cellForItemAtIndexPath: to handle highlighting and dehighlighting reuse cells: cellForItemAtIndexPath:处理突出显示和取消高亮显示重用单元格:

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

And then handle cell highlighting and dehighlighting in the didDeselectItemAtIndexPath: and didSelectItemAtIndexPath: 然后在didDeselectItemAtIndexPath:didSelectItemAtIndexPath:处理单元格突出显示和取消高亮显示didSelectItemAtIndexPath:

This works like a charm for me. 这对我来说就像一个魅力。

I had a horizontal scrolling collection view (I use collection view in Tableview) and I too faced problems withcell reuse, whenever I select one item and scroll towards right, some other cells in the next visible set gets select automatically. 我有一个水平滚动集合视图(我在Tableview中使用集合视图),我也遇到了单元重用的问题,每当我选择一个项目并向右滚动时,下一个可见集合中的一些其他单元格会自动选择。 Trying to solve this using any custom cell properties like "selected", highlighted etc didnt help me so I came up with the below solution and this worked for me. 尝试使用任何自定义单元格属性,如“选定”,突出显示等解决这个问题并没有帮助我,所以我提出了以下解决方案,这对我有用。

Step1: 步骤1:

Create a variable in the collectionView to store the selected index, here I have used a class level variable called selectedIndex 在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 -anoop

What I did to solve this was to make the changes in the customized cell. 我为解决这个问题所做的是在自定义单元格中进行更改。 You have a custom cell called DataSetCell in its class you could do the following (the code is in swift) 您在其类中有一个名为DataSetCell的自定义单元格,您可以执行以下操作(代码在swift中)

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

What this does is that every time the cell is selected, deselected, initialized or get called from the reusable queue, that code will run and the changes will be made. 这样做的是,每次从可重用队列中选择,取消选择,初始化或调用单元格时,该代码将运行并进行更改。 Hope this helps you. 希望这对你有所帮助。

In your custom cell create public method: 在自定义单元格中创建公共方法:

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

Also write redefenition of -prepareForReuse cell method: 还要编写-prepareForReuse单元格方法的重新授权:

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

And in your ViewController you should have _selectedIndexPath variable, which defined in -didSelectItemAtIndexPath and nullified in -didDeselectItemAtIndexPath 在你的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;
}

Only @ stefanB solution worked for me on iOS 9.3 只有@ stefanB解决方案适用于iOS 9.3

Here what I have to change for Swift 2 在这里我要为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
 }

The problem you encounter comes from the lack of call to super.prepareForReuse() . 您遇到的问题来自缺少对super.prepareForReuse()的调用。

Some other solutions above, suggesting to update the UI of the cell from the delegate's functions, are leading to a flawed design where the logic of the cell's behaviour is outside of its class. 上面的一些其他解决方案,建议从委托的函数更新单元的UI,导致了一个有缺陷的设计,其中单元格行为的逻辑超出了它的类。 Furthermore, it's extra code that can be simply fixed by calling super.prepareForReuse() . 此外,它是额外的代码,可以通过调用super.prepareForReuse()简单地修复。 For example : 例如 :

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
    }

}

With such design, you can even leave the delegate's functions collectionView:didSelectItemAt: / collectionView:didDeselectItemAt: all empty, and the selection process will be totally handled, and behave properly with the cells recycling. 通过这样的设计,您甚至可以将委托的函数collectionView:didSelectItemAt: / collectionView:didDeselectItemAt:全部清空,并且选择过程将完全处理,并且在单元格回收时表现正常。

you can just set the selectedBackgroundView of the cell to be backgroundColor=x. 你可以将单元格的selectedBackgroundView设置为backgroundColor = x。

Now any time you tap on cell his selected mode will change automatically and will couse to the background color to change to x. 现在,无论何时点击单元格,他所选择的模式都会自动更改,并将显示为背景颜色以更改为x。

Thanks to your answer @ RDC . 感谢您的回答@ RDC

The following codes works with Swift 3 以下代码适用于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
}

Changing the cell property such as the cell's background colors shouldn't be done on the UICollectionViewController itself, it should be done inside you CollectionViewCell class. 更改单元属性(如单元格的背景颜色)不应该在UICollectionViewController本身上完成,它应该在CollectionViewCell类中完成。 Don't use didSelect and didDeselect, just use this: 不要使用didSelect和didDeselect,只需使用:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM