我有一个带有两个集合视图的简单屏幕。我想要的是,当我在第一个CV中选择一个项目时,我想显示一个选择指示器并在第二个CV中多次显示该项目,如下面的屏幕截图所示(忽略图像中的透明度):
这是我的代码(它有点长,但很简单):
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
@IBOutlet weak var cv1: UICollectionView!
@IBOutlet weak var cv2: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
cv1.dataSource = self
cv1.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let layout = cv1.collectionViewLayout as! UICollectionViewFlowLayout
var size = layout.itemSize
size.width = cv1.bounds.width / CGFloat(items.count)
layout.itemSize = size
layout.invalidateLayout()
cv1.reloadData()
}
let items = ["A", "B", "C", "D", "E", "F", "G", "E", "H", "I"]
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CollectionViewCell
cell.setText(collectionView == cv1 ? items[indexPath.row] : items[currentSelection])
return cell
}
var currentSelection = -1
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if currentSelection != -1 {
let oldCell = collectionView.cellForItemAtIndexPath(NSIndexPath(forRow: currentSelection, inSection: 0)) as? CollectionViewCell
oldCell?.makeSelect(false)
}
var shouldSelect = true
if indexPath.row != currentSelection {
currentSelection = indexPath.row
}
else {
currentSelection = -1
shouldSelect = false
}
let cell = collectionView.cellForItemAtIndexPath(indexPath) as? CollectionViewCell
cell?.makeSelect(shouldSelect)
// if you comment the block of code bellow the selection works fine
if collectionView == cv1 {
if shouldSelect {
cv2.dataSource = self
cv2.delegate = self
cv2.reloadData()
cv2.alpha = 1
}
else {
cv2.dataSource = nil
cv2.delegate = nil
cv2.alpha = 0
}
}
}
}
class CollectionViewCell: UICollectionViewCell {
@IBOutlet weak var label: UILabel!
func setText(str: String) {
label.text = str
}
func makeSelect(selected: Bool) {
contentView.backgroundColor = selected ? UIColor.yellowColor() : UIColor.clearColor()
}
}
问题是当你运行项目并选择带有字母D的单元格时,会发生什么:
如果在方法viewDidLayoutSubviews
内删除以下行,则一切正常:
cv1.reloadData()
但是,在我的真实项目中,我需要在这个地方调用reloadData()
函数。
我认为问题不在于此调用,因为如果您对代码中标记的块进行注释,那么会出现第二个集合视图的块,您将看到第一个集合视图中的选择正常工作而不删除reloadData()
致电。如果为单元格使用不同的重用标识符,也会出现此问题。
我的问题是:这里发生了什么?
答案 0 :(得分:1)
这里发生了一些事情:
开始之前,根据Apple:
集合视图的数据源对象提供了两种内容 项目和用于呈现该内容的视图。什么时候收藏 查看首先加载其内容,它要求其数据源提供 每个可见项目的查看。
简化代码的创建过程,即集合视图 要求您始终将视图出列,而不是创建它们 明确地在你的代码中。出站视图有两种方法。 您使用的那个取决于请求的视图类型:
dequeueReusableCell(withReuseIdentifier:为:)。
dequeueReusableSupplementaryView(ofKind:withReuseIdentifier:为:)。
现在,让我们看看您的代码是如何执行的:
当应用程序启动时,您有一个集合视图(cv1
),显示从A到I的字母,蓝色背景。
如果点按任何单元格collectionView(collectionView:, didSelectItemAtIndexPath: )
,则会在此处更改单元格的颜色:cell?.makeSelect(shouldSelect)
。稍后,在此功能的某个时刻,您可以设置cv2
的数据源:cv2.dataSource = self
第一次在第二个集合视图上设置数据源时,会创建CollectionViewCell
的新实例,因此调用viewDidLayoutSubviews
,但在此函数中,您调用cv1.reloadData()
。< / p>
此调用将使cv1
中的单元格重用,之前更改颜色的单元格可能会用于另一个字母(这就是您选择另一个字母的原因)。
这只是第一次发生,因为之后cv2
中的单元格已经创建并重复使用,因此viewDidLayoutSubviews
不会被调用。
快速修复将数据源设置为cv2
中与ViewDidLoad
一样的第二个集合视图(cv1
):
cv2.dataSource = self
cv2.delegate = self
这将创建CollectionViewCell
的新实例,因此当您重置cv2
中collectionView(collectionView: , didSelectItemAtIndexPath:)
的数据源时,将会创建单元格并且viewDidLayoutSubviews
不会被触发。
好的,这只是一种解决方法,并没有真正解决问题,如果由于任何原因创建了新的单元格,问题将再次发生。
解决此问题的正确方法是准备要重复使用的单元格并重新选择当前值,如下所示:
class CollectionViewCell: UICollectionViewCell {
...
override func prepareForReuse() {
super.prepareForReuse()
contentView.backgroundColor = UIColor.clearColor()
label?.text = nil
}
}
而且,在collectionView(collectionView:, cellForItemAtIndexPath: )
:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
...
if collectionView == cv1 && indexPath.row == currentSelection {
cell.makeSelect(true)
}
return cell
}