在 UICollectionViewController 中只选择一项

Posted

技术标签:

【中文标题】在 UICollectionViewController 中只选择一项【英文标题】:Select only one item in UICollectionViewController 【发布时间】:2017-05-26 15:47:48 【问题描述】:

我已经实现了 UICollectionViewController 的一个子类,它可以水平滚动,我希望它一次只能选择一个项目。

当我更改当前屏幕上的选定项目时,它工作正常。但是,例如,如果我在集合的最开始选择一个项目,然后向右滚动并选择另一个项目,则仍然会选择第一个项目。

这是我的 CollectionView 的当前版本:

class GenresCollectionVC: UICollectionViewController 

    var selectedIndexPath: IndexPath?

    // MARK: UICollectionViewDataSource
    override func numberOfSections(in collectionView: UICollectionView) -> Int 
        return 1
    

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return MockData.instance.genres.count
    

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: reuseIdentifier, for: indexPath) as! GenreCollectionViewCell

        cell.genreNameLabel.text = MockData.instance.genres[indexPath.row]
        if selectedIndexPath == indexPath 
            redraw(selectedCell: cell)
         else 
            redraw(deselectedCell: cell)
        

        return cell
    

    // MARK: UICollectionViewDelegate
    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        guard let cell = collectionView.cellForItem(at: indexPath) as? GenreCollectionViewCell else 
            return
        
        redraw(selectedCell: cell)
        selectedIndexPath = indexPath
    

    override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) 
        guard let cell = collectionView.cellForItem(at: indexPath) as? GenreCollectionViewCell else 
            return
        
        redraw(deselectedCell: cell)
        selectedIndexPath = nil
    

    private func redraw(selectedCell cell: GenreCollectionViewCell
        ) 
        cell.layer.borderWidth = 1.0
        cell.layer.cornerRadius = cell.bounds.height / 2
        cell.layer.borderColor = UIColor.violetNeeoColor.cgColor

        cell.genreNameLabel.textColor = UIColor.violetNeeoColor
    

    private func redraw(deselectedCell cell: GenreCollectionViewCell) 
        cell.layer.borderWidth = 0.0
        cell.layer.cornerRadius = 0.0

        cell.genreNameLabel.textColor = UIColor.white
    

我做错了什么?

【问题讨论】:

【参考方案1】:

在您的 GenreCollectionViewCell 类中,覆盖 isSelected 属性,即

override var isSelected: Bool
    willSet
        super.isSelected = newValue
        if newValue
        
            self.layer.borderWidth = 1.0
            self.layer.cornerRadius = self.bounds.height / 2
            self.layer.borderColor = UIColor.violetNeeoColor.cgColor
            self.genreNameLabel.textColor = UIColor.violetNeeoColor
        
        else
        
            self.layer.borderWidth = 0.0
            self.layer.cornerRadius = 0.0
            self.genreNameLabel.textColor = UIColor.white
        
    

现在您无需在didSelectItemAt delegate 方法中手动选择/取消选择单元格。 isSelected 属性会自动处理。

【讨论】:

酷,它帮助了我!但是可以解释为什么我们应该打电话给super.isSelected = newValue?现在删除这行代码不会改变我的程序中的任何内容 如果你删除这一行,什么都没有改变意味着程序不工作? 不,当我使用您的代码时一切正常。即使我删除super.isSelected = newValue 行,一切仍然有效。所以问题是:“我为什么要写这行代码,它有什么变化?” 你其实不需要那行,我想@PGDev 忘了他是用 willSet 而不是 set,super.isSelected = newValue 会在隐式set中自动调用【参考方案2】:

在这种情况下,保存 IndexPath 然后使用它作为比较的基础对我来说始终是最佳选择。

首先定义你的变量:

var _selectedIndexPath : IndexPath? = nil

然后在你的didSelectItemAtIndexPath:

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)

            if ((_selectedIndexPath) != nil)

                if indexPath.compare(_selectedIndexPath!) == ComparisonResult.orderedSame 

                    //if the user tap the same cell that was selected previously deselect it.

                    _selectedIndexPath = nil;
                
                else
                
\                    // save the currently selected indexPath

                    _selectedIndexPath = indexPath

                
            
            else

                // else, savee the indexpath for future reference if we don't have previous selected cell

                _selectedIndexPath = indexPath;
        

        // and now only reload only the visible cells

            collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems)
        

最后检查cellForItemAtIndexPath中的选定单元格:

   func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 


        // Demo implementation
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath as IndexPath)

        if _selectedIndexPath == indexPath

            //If the cell is selected
            cell.isSelected = true
            cell.backgroundColor = UIColor.red
        
        else
            // If the cell is not selected
            cell.isSelected=false
            cell.backgroundColor = UIColor.gray


        
        return cell
    

【讨论】:

这是正确的答案,但 didSelectItem 有点不稳定。这是我的要点以及更好的版本:gist.github.com/PeeJWeeJ/414897cfeba33a8ea8edb51239a07612 @GetSwifty 你为什么要删除你的要点,PJ?【参考方案3】:

选择新的indexPath后手动取消选择单元格

override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
    guard let cell = collectionView.cellForItem(at: indexPath) as? GenreCollectionViewCell else 
        return
    
    if selectedIndexPath != indexPath 
        collectionView(collectionView, didDeselectItemAt: selectedIndexPath)
     
    redraw(selectedCell: cell)
    selectedIndexPath = indexPath

显然,每点击一次单元格就会调用一次取消选择委托方法。

【讨论】:

【参考方案4】:
private var selectedIndexPath: IndexPath? = nil

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tagcell", for: indexPath) as! TagCell
        cell.title = items[indexPath.item]
        cell.isSelected = selectedIndexPath == indexPath
        return cell
    

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
        if selectedIndexPath != nil 
            if indexPath != selectedIndexPath 
                selectedIndexPath = indexPath
            
         else 
            selectedIndexPath = indexPath
        

        collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems)
    

【讨论】:

以上是关于在 UICollectionViewController 中只选择一项的主要内容,如果未能解决你的问题,请参考以下文章

NOIP 2015 & SDOI 2016 Round1 & CTSC 2016 & SDOI2016 Round2游记

秋的潇洒在啥?在啥在啥?

上传的数据在云端的怎么查看,保存在啥位置?

在 React 应用程序中在哪里转换数据 - 在 Express 中还是在前端使用 React?

存储在 plist 中的数据在模拟器中有效,但在设备中无效

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据