簡體   English   中英

UICollectionView單元選擇和單元重用

[英]UICollectionView cell selection and cell reuse

選擇單元格后,我想處理更改單元格外觀。 我想到了委托方法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;
}

並且選擇狀態按預期工作。

有任何想法嗎?

你的觀察是正確的。 由於重用單元格,這種情況正在發生。 但是你不必對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
}

設置單元格的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;

你只需要在你的類中實現UICollectionViewDelegate啟用單元格突出顯示並選擇如下:

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

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

這對我有用。

UICollectionView在iOS 10中發生了變化,為上述解決方案帶來了一些問題。

這是一個很好的指南: https//littlebitesofcocoa.com/241-uicollectionview-cell-pre-fetching

離開屏幕后,細胞現在可以保持一段時間。 這意味着有時我們可能無法在didDeselectItemAt indexPath中獲取一個單元格來調整它。 然后它可以在屏幕上顯示未更新和未回收。 prepareForReuse沒有幫助這個角落的情況。

最簡單的解決方案是通過將isPrefetchingEnabled設置為false來禁用新滾動。 有了這個,使用cellForItemAt管理單元格的顯示didSelect didDeselect就像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!
}

通過上述操作,您可以在選擇單元格時控制單元格,在屏幕上取消選擇,回收並重新顯示。

Anil走在正確的軌道上(他的解決方案看起來應該可行,我開發了獨立於他的解決方案)。 我仍然使用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:處理單元格突出顯示和取消高亮顯示didSelectItemAtIndexPath:

這對我來說就像一個魅力。

我有一個水平滾動集合視圖(我在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

我為解決這個問題所做的是在自定義單元格中進行更改。 您在其類中有一個名為DataSetCell的自定義單元格,您可以執行以下操作(代碼在swift中)

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

這樣做的是,每次從可重用隊列中選擇,取消選擇,初始化或調用單元格時,該代碼將運行並進行更改。 希望這對你有所幫助。

在自定義單元格中創建公共方法:

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

只有@ 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
 }

您遇到的問題來自缺少對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:全部清空,並且選擇過程將完全處理,並且在單元格回收時表現正常。

你可以將單元格的selectedBackgroundView設置為backgroundColor = x。

現在,無論何時點擊單元格,他所選擇的模式都會自動更改,並將顯示為背景顏色以更改為x。

感謝您的回答@ 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
}

更改單元屬性(如單元格的背景顏色)不應該在UICollectionViewController本身上完成,它應該在CollectionViewCell類中完成。 不要使用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