UICollectionView设透明背景时出现滚动初始位置内容残影问题
问题描述
UICollectionView默认背景色为.systemBackground时显示正常,将背景色设置为.clear后没有出现预期的透明效果,背景层反而出现滚动视图初始位置首批单元格的“鬼影”残影——也就是背景直接显示成滚动视图初始位置的截图效果。

相关实现代码
UICollectionView 协议实现
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 30 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LineCell", for: indexPath) as! LineCell cell.changeSize(indexPath.row) cell.text.text = String(indexPath.row) return cell }
视图初始化与添加代码
let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.minimumLineSpacing = 0 layout.minimumInteritemSpacing = 0 layout.estimatedItemSize = CGSize(width: 50, height: 80) numberPicker = UICollectionView(frame: .zero, collectionViewLayout: layout) secondsControlHolder.addSubview(numberPicker!) numberPicker?.delegate = delegate numberPicker?.dataSource = delegate numberPicker?.register(LineCell.self, forCellWithReuseIdentifier: "LineCell") numberPicker?.translatesAutoresizingMaskIntoConstraints = false numberPicker?.heightAnchor.constraint(equalToConstant: 120).isActive = true numberPicker?.bottomAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor).isActive = true numberPicker?.leadingAnchor.constraint(equalTo: secondsControlHolder.leadingAnchor).isActive = true numberPicker?.trailingAnchor.constraint(equalTo: secondsControlHolder.trailingAnchor).isActive = true numberPicker?.backgroundColor = .clear numberPicker?.showsVerticalScrollIndicator = false numberPicker?.showsHorizontalScrollIndicator = false numberPicker?.isPagingEnabled = false numberPicker?.contentInset = UIEdgeInsets(top: 0, left: (delegate.view.frame.size.width)/2, bottom: 0, right: (delegate.view.frame.size.width)/2) let arrow = UIView() let arrowTriangle = UIImageView(image: UIImage(systemName: "arrowtriangle.up.fill")) secondsControlHolder.addSubview(arrow) secondsControlHolder.addSubview(arrowTriangle) arrow.translatesAutoresizingMaskIntoConstraints = false arrow.bottomAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor, constant: -10).isActive = true arrow.centerXAnchor.constraint(equalTo: secondsControlHolder.centerXAnchor).isActive = true arrow.heightAnchor.constraint(equalToConstant: 100).isActive = true arrow.backgroundColor = .label arrow.widthAnchor.constraint(equalToConstant: 5).isActive = true arrow.isUserInteractionEnabled = false arrowTriangle.translatesAutoresizingMaskIntoConstraints = false arrowTriangle.centerXAnchor.constraint(equalTo: arrow.centerXAnchor).isActive = true arrowTriangle.topAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor, constant: -130).isActive = true arrowTriangle.tintColor = .label arrowTriangle.heightAnchor.constraint(equalToConstant: 23).isActive = true arrowTriangle.widthAnchor.constraint(equalToConstant: 30).isActive = true
自定义LineCell实现
import UIKit class LineCell: UICollectionViewCell { let lineHolder = UIView() let text = UILabel() override init(frame: CGRect) { super.init(frame: frame) text.text = "test" text.textColor = .white lineHolder.translatesAutoresizingMaskIntoConstraints = false lineHolder.addSubview(text) text.translatesAutoresizingMaskIntoConstraints = false text.topAnchor.constraint(equalTo: lineHolder.topAnchor).isActive = true text.bottomAnchor.constraint(equalTo: lineHolder.bottomAnchor).isActive = true text.leadingAnchor.constraint(equalTo: lineHolder.leadingAnchor).isActive = true text.trailingAnchor.constraint(equalTo: lineHolder.trailingAnchor).isActive = true addSubview(lineHolder) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupLineHolder(){ lineHolder.frame = CGRect(x: 0, y: 0, width: 1, height: 80) lineHolder.backgroundColor = .label } func changeSize(_ index: Int){ if index % 5 == 0 { lineHolder.frame = CGRect(x: 0, y: 0, width: 50, height: 80) lineHolder.backgroundColor = .label }else{ lineHolder.frame = CGRect(x: 0, y: 15, width: 50, height: 50) lineHolder.backgroundColor = .label.withAlphaComponent(0.7) } } }
问题原因与修复方案
根本原因
残影问题由三个代码缺陷共同触发:
- 自定义Cell的
contentView未设置明确背景色,Cell本身背景色默认是nil,滚动复用时系统无法正确清除Cell内容的渲染缓存。 - Cell内混用AutoLayout和手动布局:
lineHolder设置了translatesAutoresizingMaskIntoConstraints = false,但后续changeSize方法中直接修改lineHolder.frame,也没有给lineHolder设置对应的宽高、位置约束,导致渲染系统无法正确计算视图层级的脏区域,滚动时旧渲染内容没有被擦除,形成鬼影。 - CollectionView开启了尺寸预估
estimatedItemSize,但没有实现完整的Self Sizing逻辑,滚动过程中布局属性更新不完整,进一步加重渲染缓存不刷新的问题。
修复步骤
- 给Cell本身和
contentView设置透明背景,避免渲染缓存残留,在LineCell的init方法中添加如下代码:backgroundColor = .clear contentView.backgroundColor = .clear - 统一布局方式,不要混用AutoLayout和手动修改frame。要么全程用AutoLayout给
lineHolder添加完整的位置、尺寸约束,在changeSize方法中更新约束常量;要么把lineHolder的translatesAutoresizingMaskIntoConstraints设为true,使用纯frame布局。纯frame方案只需删除init方法中lineHolder.translatesAutoresizingMaskIntoConstraints = false这一行即可。 - 如果不需要Self Sizing自适应尺寸,直接将FlowLayout的
estimatedItemSize设为.zero,关闭预估尺寸,避免布局计算异常:layout.estimatedItemSize = .zero - 若完成上述修改后仍有残影,可给CollectionView添加配置,强制滚动时重绘内容:
numberPicker?.contentMode = .redraw
内容的提问来源于stack exchange,提问作者Douglas Gondim
相关产品推荐
相关产品推荐

