ios - 使其布局无效时与 UICollectionView 单元格选择相关的问题

标签 ios swift uicollectionview

我有一个带有两个 Collection View 的简单屏幕。我想要的是,当我在第一个 CV 中选择一个项目时,我想显示一个选择指示器并在第二个 CV 中多次显示该项目,如下面的屏幕截图所示(忽略图像中的透明度):

enter image description here

这是我的代码(有点长但是很简单):

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 的单元格时,会发生以下情况:

enter image description here

如果在方法 viewDidLayoutSubviews 中删除以下行,一切正常:

cv1.reloadData()

但是,在我的实际项目中,我需要在这个地方调用reloadData()函数。

我认为问题不在于此调用,因为如果您评论代码中标记的 block ,即使第二个 Collection View 出现的 block ,您将看到第一个 Collection View 中的选择工作正常而无需删除reloadData() 调用。如果您对单元格使用不同的重用标识符,也会出现此问题。

我的问题是:这是怎么回事?

最佳答案

这里发生了一些事情:

开始前,根据Apple :

The collection view’s data source object provides both the content for items and the views used to present that content. When the collection view first loads its content, it asks its data source to provide a view for each visible item.

To simplify the creation process for your code, the collection view requires that you always dequeue views, rather than create them explicitly in your code. There are two methods for dequeueing views. The one you use depends on which type of view has been requested:

  • dequeueReusableCell(withReuseIdentifier:for:).

  • dequeueReusableSupplementaryView(ofKind:withReuseIdentifier:for:).

现在,让我们看看您的代码是如何执行的:

当应用程序启动时,您有一个 Collection View (cv1),显示蓝色背景中从 A 到 I 的字母。

如果你点击任何单元格 collectionView(collectionView:, didSelectItemAtIndexPath: ) 被触发,在这里你改变单元格的颜色:cell?.makeSelect(shouldSelect) .稍后,在此函数中的某个时刻,您为 cv2 设置了数据源:cv2.dataSource = self

第一次在第二个 Collection View 上设置数据源时,创建了 CollectionViewCell 的新实例,因此调用了 viewDidLayoutSubviews,但在此函数中您调用了 cv1.reloadData().

此调用将使 cv1 中的单元格被重复使用,您之前更改颜色的单元格可能会用于另一个字母(这就是您看到另一个字母被选中的原因)。

这只会在第一次发生,因为在那之后,cv2 中的单元格已经被创建和重用,所以 viewDidLayoutSubviews 不会被调用。

快速修复是将数据源设置为 ViewDidLoad 中的第二个 Collection View (cv2),就像您对 cv1 所做的那样:

cv2.dataSource = self
cv2.delegate = self

这将创建 CollectionViewCell 的新实例,因此当您在 collectionView(collectionView: , didSelectItemAtIndexPath:) 单元格中为 cv2 重置数据源时将已经创建并且 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
}

关于ios - 使其布局无效时与 UICollectionView 单元格选择相关的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39754932/

相关文章:

ios - 属性上的 Swift 扩展事件监听器

ios - UITableView 单元格显示错误的图像

swift - 对象位置不匹配(Spritekit)

ios - 无法在 watchOS 模拟器上安装应用程序 Xcode9、iOS11、watchOS4 应用程序崩溃

ios - 在 CoreData 中修改子项的父属性值

ios - 使用跟踪。 applicationDidBecomeActive 和/或 didFinishLaunchingWithOptions

ios - Swift - tableview contentsize 不匹配内容?

ios - 跨多个 UIViewController 的统一 UICollectionView

ios - iPhone 照片库应用程序需要帮助,请

ios - 更改 View 的 y 位置会删除点击事件