You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
        }
    }
}

问题原因与修复方案

根本原因

残影问题由三个代码缺陷共同触发:

  1. 自定义Cell的contentView未设置明确背景色,Cell本身背景色默认是nil,滚动复用时系统无法正确清除Cell内容的渲染缓存。
  2. Cell内混用AutoLayout和手动布局:lineHolder设置了translatesAutoresizingMaskIntoConstraints = false,但后续changeSize方法中直接修改lineHolder.frame,也没有给lineHolder设置对应的宽高、位置约束,导致渲染系统无法正确计算视图层级的脏区域,滚动时旧渲染内容没有被擦除,形成鬼影。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:01:46