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

UICollectionView调用reloadData后异常显示多余图片求助

问题根源与解决方案

核心问题

你遇到的多余图片问题,本质是UICollectionView的单元格复用机制导致的内容堆叠:

  • 每次调用cellForItemAt时,你直接给cell的contentView添加新UIImageView、给collectionView添加新按钮,还给cell的layer添加新虚线层;
  • 当单元格被复用时,这些旧的子视图/图层没有被清除,新内容叠加在旧内容上,就出现了多余图片。

具体修复方案

1. 自定义UICollectionViewCell(推荐方案)

不要在cellForItemAt里动态创建子视图,提前在自定义Cell中布局好所有需要的控件,通过配置Cell状态来切换显示内容,从根源避免重复添加。

自定义Cell代码

import UIKit

class PhotoCollageCell: UICollectionViewCell {
    static let reuseIdentifier = "imageUserCell"
    
    // 预定义所有需要的控件
    private let imageView: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFill
        iv.clipsToBounds = true
        iv.translatesAutoresizingMaskIntoConstraints = false
        return iv
    }()
    
    private let actionButton: UIButton = {
        let btn = UIButton(type: .system)
        btn.layer.cornerRadius = 15
        btn.layer.masksToBounds = true
        btn.layer.shadowColor = UIColor.black.cgColor
        btn.layer.shadowOffset = .zero
        btn.layer.opacity = 1
        btn.layer.shadowRadius = 10
        btn.translatesAutoresizingMaskIntoConstraints = false
        return btn
    }()
    
    private var dottedBorderLayer: CAShapeLayer?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupUI()
    }
    
    private func setupUI() {
        layer.cornerRadius = 10
        layer.masksToBounds = true
        
        contentView.addSubview(imageView)
        contentView.addSubview(actionButton)
        
        // 自动布局约束
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            
            actionButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5),
            actionButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
            actionButton.widthAnchor.constraint(equalToConstant: 30),
            actionButton.heightAnchor.constraint(equalToConstant: 30)
        ])
    }
    
    // 复用前重置所有状态
    override func prepareForReuse() {
        super.prepareForReuse()
        imageView.image = nil
        imageView.isHidden = true
        actionButton.setImage(nil, for: .normal)
        actionButton.backgroundColor = nil
        actionButton.layer.borderWidth = 0
        dottedBorderLayer?.removeFromSuperlayer()
        dottedBorderLayer = nil
        backgroundColor = nil
    }
    
    // 配置有图片的Cell状态
    func setupWithImage(_ image: UIImage, deleteAction: UIAction) {
        imageView.image = image
        imageView.isHidden = false
        
        actionButton.backgroundColor = .white
        actionButton.setImage(UIImage(named: "DeletePhoto1"), for: .normal)
        actionButton.tintColor = .gray
        actionButton.layer.borderWidth = 0.5
        actionButton.layer.borderColor = UIColor.gray.cgColor
        
        actionButton.removeAllActions()
        actionButton.addAction(deleteAction, for: .touchUpInside)
    }
    
    // 配置空Cell状态
    func setupEmpty(addAction: UIAction) {
        backgroundColor = UIColor(named: "PhotoCollage")
        
        // 添加虚线边框
        let borderLayer = CAShapeLayer()
        borderLayer.strokeColor = UIColor.gray.cgColor
        borderLayer.lineDashPattern = [10,4]
        borderLayer.frame = bounds
        borderLayer.opacity = 0.4
        borderLayer.lineWidth = 5
        borderLayer.fillColor = nil
        borderLayer.path = UIBezierPath(rect: borderLayer.bounds).cgPath
        layer.addSublayer(borderLayer)
        dottedBorderLayer = borderLayer
        
        actionButton.backgroundColor = UIColor(named: "MainAppColor")
        actionButton.setImage(UIImage(named: "Plus"), for: .normal)
        actionButton.tintColor = .white
        
        actionButton.removeAllActions()
        actionButton.addAction(addAction, for: .touchUpInside)
    }
}

2. 修改ViewController代码

import UIKit

class SettingsPhotoViewController: UIViewController {
    
    @IBOutlet weak var collectionPhotoView: UICollectionView!
    
    let imagePicker = UIImagePickerController()
    var imageArr = [UIImage(named: "1")!,UIImage(named: "2")!,UIImage(named: "3")!,UIImage(named: "4")!]
    
    override func viewDidLoad() {
        super.viewDidLoad()

        collectionPhotoView.delegate = self
        collectionPhotoView.dataSource = self
        // 注册自定义Cell
        collectionPhotoView.register(PhotoCollageCell.self, forCellWithReuseIdentifier: PhotoCollageCell.reuseIdentifier)
        
        imagePicker.delegate = self
        imagePicker.allowsEditing = false
    }
}

extension SettingsPhotoViewController : UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 9
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCollageCell.reuseIdentifier, for: indexPath) as! PhotoCollageCell
        
        if indexPath.row < imageArr.count {
            // 配置有图片的Cell
            let deleteAction = UIAction { [weak self] _ in
                print("Delete photo at index \(indexPath.row)")
                // 这里可以添加实际删除逻辑:移除数组元素后刷新
                // self?.imageArr.remove(at: indexPath.row)
                // self?.collectionPhotoView.reloadData()
            }
            cell.setupWithImage(imageArr[indexPath.row], deleteAction: deleteAction)
        } else {
            // 配置空Cell
            let addAction = UIAction { [weak self] _ in
                print("Add photo tapped")
                // 这里可以添加图片选择逻辑,比如弹出imagePicker
                // self?.present(self?.imagePicker ?? UIImagePickerController(), animated: true)
                self?.collectionPhotoView.reloadData()
            }
            cell.setupEmpty(addAction: addAction)
        }
        
        return cell
    }
    
    // 以下布局方法保持不变
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: (collectionView.frame.size.width / 3) - 14 , height: (collectionView.frame.height / 3) - 20)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 15
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: 3, bottom: 0, right: 10)
    }
}

// 补充图片选择器代理实现
extension SettingsPhotoViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let selectedImage = info[.originalImage] as? UIImage {
            imageArr.append(selectedImage)
            collectionPhotoView.reloadData()
        }
        picker.dismiss(animated: true)
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true)
    }
}

关键修复点

  • 控件复用管理:通过prepareForReuse方法在Cell复用前重置所有状态,彻底清除旧内容;
  • 控件归属修正:按钮作为Cell的子视图,随Cell一起复用,不会在CollectionView上重复堆叠;
  • 状态分离:用专门的方法配置Cell的两种状态,逻辑清晰,避免条件判断混乱。

内容的提问来源于stack exchange,提问作者Denim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 04:10:09