ios UICollectionView 单元格选择和单元格重用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15665181/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UICollectionView cell selection and cell reuse
提问by Padin215
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?
有任何想法吗?
回答by Anil Varghese
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. Instead do your check in cellForItemand set the properties accordingly. Some thing like..
你的观察是正确的。这种行为是由于单元格的重用而发生的。但是您不必对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
}
回答by stefanB
Framework will handle switching the views for youonce you setup your cell's backgroundView
and selectedBackgroundView
, see example from Managing the Visual State for Selections and Highlights:
一旦您设置了单元格的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;
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.
这对我有用。
回答by Aleksander Niedziolko
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. It can then show up on screen un-updated and un-recycled. prepareForReuse
does not help this corner case.
离开屏幕后,细胞现在会停留一段时间。这意味着有时我们可能无法获取单元格didDeselectItemAt indexPath
以对其进行调整。然后它可以显示在屏幕上未更新和未回收。prepareForReuse
无助于这种极端情况。
The easiest solution is disabling the new scrolling by setting isPrefetchingEnabled
to false. With this, managing the cell's display with
cellForItemAt
didSelect
didDeselect
works as it used to.
最简单的解决方案是通过设置isPrefetchingEnabled
为 false来禁用新的滚动。有了这个,cellForItemAt
didSelect
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.
使用上面的内容,您可以在单元格被选中、在屏幕上取消选中、回收和重新显示时控制单元格。
回答by Padin215
Anil was on the right track (his solution looks like it should work, I developed this solution independently of his). 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.
Anil 走在正确的轨道上(他的解决方案看起来应该可行,我独立于他开发了这个解决方案)。我仍然使用该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:
and 中处理单元格突出显示和取消突出显示didSelectItemAtIndexPath:
This works like a charm for me.
这对我来说就像一种魅力。
回答by anoop4real
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. 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.
我有一个水平滚动的集合视图(我在 Tableview 中使用集合视图)并且我也面临单元格重用的问题,每当我选择一个项目并向右滚动时,下一个可见集中的其他一些单元格会自动选择。尝试使用任何自定义单元格属性(如“选定”、突出显示等)来解决此问题并没有帮助我,所以我想出了以下解决方案,这对我有用。
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
回答by Eliezer Ferra
What I did to solve this was to make the changes in the customized cell. You have a custom cell called DataSetCellin 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.
这样做的作用是,每次从可重用队列中选择、取消选择、初始化或调用单元格时,都会运行该代码并进行更改。希望这对你有帮助。
回答by landonandrey
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;
}
回答by swiftBoy
Only @stefanBsolution worked for me on 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
}
回答by Pierre-Olivier Simonard
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. Furthermore, it's extra code that can be simply fixed by calling super.prepareForReuse()
. For example :
上面的一些其他解决方案,建议从委托的函数更新单元格的 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
}
}
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:
全部保留为空,并且选择过程将完全处理,并在单元格回收时正常运行。
回答by pierre23
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. Don't use didSelect and didDeselect, just use this:
更改单元格属性(例如单元格的背景颜色)不应在 UICollectionViewController 本身上完成,而应在 CollectionViewCell 类中完成。不要使用 didSelect 和 didDeselect,只需使用这个:
class MyCollectionViewCell: UICollectionViewCell
{
override var isSelected: Bool
{
didSet
{
// Your code
}
}
}