实习过程中遇到的坑,写下来分享和备用。
开发环境为XCode 8.2.1 Swift 3.0
简化一下需要实现的效果为:点击导航栏上的按钮后修改Cell上某个控件的状态,在Demo里将它简化为修改Cell的背景色。看上去是一个很简单的实现,那么先用本以为可行的方法在Demo中实现:
CollectionViewController中部分的代码:
private var colorChange: UIColor?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
guard let colorChange = colorChange else { return cell }
// Cell被复用时修改颜色
cell.backgroundColor = colorChange
return cell
}
func changeCell(with color: UIColor) {
guard let collectionView = collectionView else { return }
colorChange = color
// 修改屏幕中显示的Cell的颜色
for cell in collectionView.visibleCells {
cell.backgroundColor = color
}
}
外部调用的代码:
// 导航栏color按钮点击回调
@IBAction func changeClick(_ sender: Any) {
guard let collectionVC = collectionVC else { return }
collectionVC.changeCell(with: UIColor.blue)
}
通过修改可见Cell以及在复用方法中加入修改颜色的操作来保证所有Cell的颜色均被修改,接着在模拟器中运行:
可以看到其中有一排的Cell颜色并没有改变,当我们将那排Cell移出屏幕后再拖会才看到它的颜色发生了应有的变化。这说明那排的Cell并没有进行复用,也没有被visibleCells的取到。
本着实现需求第一的要义,我们先来想办法把切换所有Cell颜色的效果做出来。首先可以肯定那一排Cell没有走复用方法,意味着他们没有进入复用队列,既然没有进入复用队列说明它们一定还在屏幕上,那么试着取出屏幕上collectionview所有的subviews来获取这些Cell,修改changeCell方法:
func changeCell(with color: UIColor) {
guard let collectionView = collectionView else { return }
colorChange = color
// 修改部分:
for subview in collectionView.subviews {
if let cell = subview as? UICollectionViewCell {
cell.backgroundColor = color
}
}
}
经测试,修改这部分代码后所有的Cell都会被修改颜色,那么至少在效果实现上我们已经成功了。但是使用subviews并不是一件很文明的方式,我又尝试了用一个Set集合保存indexPath后再changeCell中遍历indexPahts来获取Cell的方法,但这么做的结果和visiableCells是相同的,就不展示代码过程了。
深究其中原因,得知是IOS 10中苹果修改了collectionview的复用机制,来保证更流畅的滑动效果,具体的修改部分可以自行搜索,对于这片文章中的坑有用的部分可以总结为: 离开屏幕的Cell并不会马上进入复用队列,而会被保存一段时间,在用户突然滑回屏幕的时候显示,这部分Cell不会再走一遍复用周期,以保证用户体验上的一个提升。
我暂时没有想到subviews以外的方法在新的复用机制下取到离屏却没有进入复用队列那部分Cell的方式,如果不想用这种方法,或者说现在大部分的项目都还需要兼容IOS 9,我们可以设置isPrefetchingEnabled 为false的方法来关闭新的复用机制(其实叫 预加载)
override func viewDidLoad() {
if #available(iOS 10.0, *) {
collectionView?.isPrefetchingEnabled = false
}
}
这样设定之后离开屏幕的Cell便会马上进入复用队列了。但同时也就失去了新机制带来的更好的滑动体验,具体抉择可以根据项目自行考虑。