在 CollectionVIew 中重新加载数据时按钮闪烁
Button flickering when reloading data in CollectionVIew
我正在通过在集合视图中点击按钮来更新类别图像。我有一个 collectionView,当点击按钮并且图像发生变化时,它具有淡入动画。我也试过用reloadItem,还是闪退。
我发现问题是因为我在单元格更新中调用了reloadData,但是当按下按钮时我找不到正确的方法。
ViewController
import UIKit
import CoreData
class WordsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
@IBOutlet weak var wordsCategoryCollection: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
wordsCategoryCollection.delegate = self
wordsCategoryCollection.dataSource = self
wordsCategoryCollection.isScrollEnabled = false
wordsCategoryCollection.backgroundColor = nil
roundCounter = 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return words.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WordsCategoryCell", for: indexPath) as? WordsCategoryCell {
cell.backgroundColor = UIColor.clear
cell.wordsBtn.tag = indexPath.row
cell.wordsBtn.addTarget(self, action: #selector(changeCategoryStatus), for: .touchUpInside)
cell.updateCell(word: words[indexPath.row])
return cell
}
return UICollectionViewCell()
}
@objc func changeCategoryStatus(_ sender: UIButton!) {
debugPrint(sender.tag)
if words[sender.tag].categoryIsEnable == true {
words[sender.tag].categoryIsEnable = false
words[sender.tag].categoryImageName.removeLast()
words[sender.tag].categoryImageName.append("F")
} else {
words[sender.tag].categoryIsEnable = true
words[sender.tag].categoryImageName.removeLast()
words[sender.tag].categoryImageName.append("T")
}
debugPrint("\(words[0].categoryImageName) - \(words[0].categoryIsEnable)")
debugPrint("\(words[1].categoryImageName) - \(words[1].categoryIsEnable)")
debugPrint("\(words[2].categoryImageName) - \(words[2].categoryIsEnable)")
wordsCategoryCollection.reloadData()
//wordsCategoryCollection.reloadItems(at: [IndexPath(row: sender.tag, section: 0)])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
getWordsForTheGame()
}
@IBAction func playBtn(_ sender: Any) {
performSegue(withIdentifier: "MainVC", sender: nil)
}
}
单元格
class WordsCategoryCell: UICollectionViewCell {
@IBOutlet weak var wordsBtn: UIButton!
func updateCell(word: word) {
let image = UIImage(named: word.categoryImageName)
wordsBtn.setTitle(nil, for: .normal)
wordsBtn.setImage(image, for: .normal)
}
}
GIF
我建议您不要使用 reloadData()
来实现您想要实现的目标。相反,我会推荐这个:
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
collectionView.indexPathsForSelectedItems?.forEach({ collectionView.deselectItem(at: [=10=], animated: false) })
//this function will make it so only one cell can be selected at a time
//don't add this if you want multiple cells to be selected at the same time.
return true
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
self.checkNumberOfAnswers()
let cell = collectionView.cellForItem(at: indexPath) as! WordsCategoryCell
//animate the cell that is getting unselected here
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.checkNumberOfAnswers()
let cell = collectionView.cellForItem(at: indexPath) as! WordsCategoryCell
//animate the cell that is getting selected here
}
您可以按照您想要的方式为您选择的单元格设置动画,甚至可以更改其数据,因为您可以访问该单元格,对于未选中的单元格也是如此。
只需覆盖单元格内的 isSelected 并将其设置为在单元格被选中和未被选中时执行您想要的操作。
public override var isSelected: Bool {
didSet {
switch self.isSelected {
case true:
//do stuff when selected
case false:
//do stuff when unselected
}
}
}
编辑:
reloadData()
使用 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
中的所有代码重新创建当前加载的 所有 个单元格(在视图中,因为你正在出队),效率不高对于这个特定的用例,因为它又是 运行 额外的代码,如果它已经在视图中并且使用您的配置加载它那么就不需要设置背景颜色来清除,设置按钮标签等...每个单元格再次。您看到其他单元格发生的小动画可能是在您眼前再次创建单元格的副作用。
与其更改单词数组中的数据,不如更改已创建的单元格中的数据。
reloadData()
更适合实际重新加载新数据,例如,当您从服务器发出请求并获得一个新数组来更新您的单元格时,您将 didSet
在那个数组上,当 didSet
运行时,它将 reloadData
,用实际的新 info/data.
重新创建单元格
至于如何使用按钮,首先我建议您在单元格中添加 @objc 方法以提高可读性,因为这是您的按钮所在的位置,并使用委托在您的单元格中获得通知ViewController.
其次,您需要 indexPath 来访问单元格,而不是 indexPath.row,因此在您的单元格中创建一个名为 indexPath
类型 IndexPath
的可选变量并将其设置为 indexPath
当您的单元格正在创建时。
cell.indexPath = indexPath
现在,在您的单元格中创建一个委托,该委托具有在按下按钮时传递 indexPath 的函数,例如:
protocol categoryCellDelegate {
func buttonPressed(index: IndexPath)
}
通过遵守协议并在 VC 中调用该函数时在 VC 中获取 indexPath:
func buttonPressed(index: IndexPath) {
let cell = wordsCategoryCollection.cellForItem(at: index) as! WordsCategoryCell
//now do anything your want with this cell like change image, color, etc...
}
为什么我推荐使用 CollectionView 的 didSelectItemAt
主要是因为它已经为您提供了所有这些以及更多。
直到一个月前我都使用按钮来完成它(我已经编码 Swift 8 个月了)直到我想创建一个测验系统,其中给定部分中只有一个单元格可以一次被选中,确保前一个单元格未被选中和新单元格被选中所带来的头痛是不值得的。所以我使用了 Collections View 自己的选择方法,看到它是多么简单,它给了我我想要的一切,从那以后我开始更多地使用它。
我正在通过在集合视图中点击按钮来更新类别图像。我有一个 collectionView,当点击按钮并且图像发生变化时,它具有淡入动画。我也试过用reloadItem,还是闪退。
我发现问题是因为我在单元格更新中调用了reloadData,但是当按下按钮时我找不到正确的方法。
ViewController
import UIKit
import CoreData
class WordsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
@IBOutlet weak var wordsCategoryCollection: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
wordsCategoryCollection.delegate = self
wordsCategoryCollection.dataSource = self
wordsCategoryCollection.isScrollEnabled = false
wordsCategoryCollection.backgroundColor = nil
roundCounter = 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return words.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WordsCategoryCell", for: indexPath) as? WordsCategoryCell {
cell.backgroundColor = UIColor.clear
cell.wordsBtn.tag = indexPath.row
cell.wordsBtn.addTarget(self, action: #selector(changeCategoryStatus), for: .touchUpInside)
cell.updateCell(word: words[indexPath.row])
return cell
}
return UICollectionViewCell()
}
@objc func changeCategoryStatus(_ sender: UIButton!) {
debugPrint(sender.tag)
if words[sender.tag].categoryIsEnable == true {
words[sender.tag].categoryIsEnable = false
words[sender.tag].categoryImageName.removeLast()
words[sender.tag].categoryImageName.append("F")
} else {
words[sender.tag].categoryIsEnable = true
words[sender.tag].categoryImageName.removeLast()
words[sender.tag].categoryImageName.append("T")
}
debugPrint("\(words[0].categoryImageName) - \(words[0].categoryIsEnable)")
debugPrint("\(words[1].categoryImageName) - \(words[1].categoryIsEnable)")
debugPrint("\(words[2].categoryImageName) - \(words[2].categoryIsEnable)")
wordsCategoryCollection.reloadData()
//wordsCategoryCollection.reloadItems(at: [IndexPath(row: sender.tag, section: 0)])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
getWordsForTheGame()
}
@IBAction func playBtn(_ sender: Any) {
performSegue(withIdentifier: "MainVC", sender: nil)
}
}
单元格
class WordsCategoryCell: UICollectionViewCell {
@IBOutlet weak var wordsBtn: UIButton!
func updateCell(word: word) {
let image = UIImage(named: word.categoryImageName)
wordsBtn.setTitle(nil, for: .normal)
wordsBtn.setImage(image, for: .normal)
}
}
GIF
我建议您不要使用 reloadData()
来实现您想要实现的目标。相反,我会推荐这个:
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
collectionView.indexPathsForSelectedItems?.forEach({ collectionView.deselectItem(at: [=10=], animated: false) })
//this function will make it so only one cell can be selected at a time
//don't add this if you want multiple cells to be selected at the same time.
return true
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
self.checkNumberOfAnswers()
let cell = collectionView.cellForItem(at: indexPath) as! WordsCategoryCell
//animate the cell that is getting unselected here
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.checkNumberOfAnswers()
let cell = collectionView.cellForItem(at: indexPath) as! WordsCategoryCell
//animate the cell that is getting selected here
}
您可以按照您想要的方式为您选择的单元格设置动画,甚至可以更改其数据,因为您可以访问该单元格,对于未选中的单元格也是如此。
只需覆盖单元格内的 isSelected 并将其设置为在单元格被选中和未被选中时执行您想要的操作。
public override var isSelected: Bool {
didSet {
switch self.isSelected {
case true:
//do stuff when selected
case false:
//do stuff when unselected
}
}
}
编辑:
reloadData()
使用 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
中的所有代码重新创建当前加载的 所有 个单元格(在视图中,因为你正在出队),效率不高对于这个特定的用例,因为它又是 运行 额外的代码,如果它已经在视图中并且使用您的配置加载它那么就不需要设置背景颜色来清除,设置按钮标签等...每个单元格再次。您看到其他单元格发生的小动画可能是在您眼前再次创建单元格的副作用。
与其更改单词数组中的数据,不如更改已创建的单元格中的数据。
reloadData()
更适合实际重新加载新数据,例如,当您从服务器发出请求并获得一个新数组来更新您的单元格时,您将 didSet
在那个数组上,当 didSet
运行时,它将 reloadData
,用实际的新 info/data.
至于如何使用按钮,首先我建议您在单元格中添加 @objc 方法以提高可读性,因为这是您的按钮所在的位置,并使用委托在您的单元格中获得通知ViewController.
其次,您需要 indexPath 来访问单元格,而不是 indexPath.row,因此在您的单元格中创建一个名为 indexPath
类型 IndexPath
的可选变量并将其设置为 indexPath
当您的单元格正在创建时。
cell.indexPath = indexPath
现在,在您的单元格中创建一个委托,该委托具有在按下按钮时传递 indexPath 的函数,例如:
protocol categoryCellDelegate {
func buttonPressed(index: IndexPath)
}
通过遵守协议并在 VC 中调用该函数时在 VC 中获取 indexPath:
func buttonPressed(index: IndexPath) {
let cell = wordsCategoryCollection.cellForItem(at: index) as! WordsCategoryCell
//now do anything your want with this cell like change image, color, etc...
}
为什么我推荐使用 CollectionView 的 didSelectItemAt
主要是因为它已经为您提供了所有这些以及更多。
直到一个月前我都使用按钮来完成它(我已经编码 Swift 8 个月了)直到我想创建一个测验系统,其中给定部分中只有一个单元格可以一次被选中,确保前一个单元格未被选中和新单元格被选中所带来的头痛是不值得的。所以我使用了 Collections View 自己的选择方法,看到它是多么简单,它给了我我想要的一切,从那以后我开始更多地使用它。