web-dev-qa-db-ja.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;
}

また、選択状態は意図したとおりに機能しました。

何か案は?

48
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
}
73
Anil Varghese

フレームワークは、セルの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;
}

これでうまくいく。

24
stefanB

IOS 10でUICollectionViewが変更され、上記のソリューションにいくつかの問題が発生しています。

ここに良いガイドがあります: 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!
}

上記を使用すると、セルを選択、非選択、画面上で再利用、再表示、および再表示するときにセルを制御できます。

21

アニルは正しい軌道に乗っていました(彼のソリューションはうまくいくはずです、私は彼とは独立してこのソリューションを開発しました)。私はまだ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:およびdidSelectItemAtIndexPath:でセルのハイライトとハイライト解除を処理します

これは私にとって魅力的です。

10
Padin215

水平スクロールのコレクションビュー(Tableviewでコレクションビューを使用)があり、1つのアイテムを選択して右にスクロールすると、次の表示セットの他のセルが自動的に選択されます。 「選択」、強調表示などのカスタムセルプロパティを使用してこれを解決しようとしても、私は役に立たなかったので、以下の解決策を思い付きました。

ステップ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];

}

-アヌープ

6
anoop4real

これを解決するために行ったことは、カスタマイズされたセルに変更を加えることでした。クラスにDataSetCellというカスタムセルがあり、次のことができます(コードはSwiftにあります)

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

これにより、セルが選択、選択解除、初期化、または再利用可能なキューから呼び出されるたびに、そのコードが実行され、変更が行われます。これがお役に立てば幸いです。

3
Eliezer Ferra

カスタムセルでパブリックメソッドを作成します。

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

あなたが遭遇する問題は、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:をすべて空のままにしておくこともでき、選択プロセスは完全に処理され、セルのリサイクルで適切に動作します。

IOS 9.3では、@ stefanB ソリューションのみが機能しました

ここで私が変更しなければならないこと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