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

如何创建从数组加载数据展示一排UIImageView的类 解决CollectionViewCell布局异常问题

问题根因

你当前代码的核心问题是所有视图添加、布局计算逻辑都耦合在SDWebImage的异步图片下载回调中:

  • 图片下载返回顺序不受遍历顺序控制,导致spacing变量累加顺序错乱,头像位置不符合预期
  • 子视图添加顺序随机,导致头像层级覆盖和预期相反
  • 页面返回时图片已经有本地缓存,回调顺序和遍历顺序一致,所以展示恢复正常,和你观察到的现象完全吻合
优化实现方案

核心改造点

  • 提前创建所有头像视图,先完成布局占位,再触发图片下载,图片下载完成后直接赋值即可,不影响布局逻辑
  • 用遍历索引直接计算每个头像的左间距,不要在异步回调中累加间距变量
  • 新增数据更新逻辑,适配UICollectionViewCell复用场景,避免复用时出现旧视图残留

优化后代码

import Foundation
import UIKit

class UserStack: UIView{
    
    var size: CGFloat = 50
    // 加didSet监听,支持数据更新/Cell复用
    var userArray: [UserModel]! {
        didSet {
            // 先清空旧子视图
            subviews.forEach { $0.removeFromSuperview() }
            renderView()
        }
    }
    
    convenience init(userArray: [UserModel], size: CGFloat = 50) {
        self.init(frame: .zero)
        self.size = size
        self.userArray = userArray
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    private func renderView() {
        let maxDisplayCount = 4
        let displayCount = min(userArray.count, maxDisplayCount)
        let itemOffset = size - 10
        
        for i in 0..<displayCount {
            let isLast = i == displayCount - 1 && userArray.count > maxDisplayCount
            let currentUser = isLast ? userArray.last! : userArray[i]
            
            if !isLast {
                // 普通头像
                let imageView = createAvatarImageView()
                addSubview(imageView)
                // 提前算好位置,不受异步逻辑影响
                let leftPadding = CGFloat(i) * itemOffset
                imageView.setDimensions(height: size, width: size)
                imageView.centerY(inView: self)
                imageView.anchor(left: leftAnchor, paddingLeft: leftPadding)
                // 单独处理图片加载
                guard let url = currentUser.imageURL else { return }
                imageView.sd_setImage(with: URL(string: url))
            } else {
                // 带+N标识的末尾头像
                let numberImageView = createAvatarImageView()
                addSubview(numberImageView)
                let leftPadding = CGFloat(i) * itemOffset
                numberImageView.setDimensions(height: size, width: size)
                numberImageView.centerY(inView: self)
                numberImageView.anchor(left: leftAnchor, paddingLeft: leftPadding)
                
                // 系统模糊层效果更自然
                let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
                blurView.layer.cornerRadius = size * 0.4
                blurView.layer.masksToBounds = true
                blurView.layer.borderWidth = 0.75
                blurView.layer.borderColor = UIColor.white.cgColor
                numberImageView.addSubview(blurView)
                blurView.setDimensions(height: size, width: size)
                blurView.center(inView: numberImageView)
                
                let numberLabel = UILabel()
                numberLabel.text = "+\(userArray.count - 3)"
                numberLabel.font = .poppinsMedium(size: 14)
                numberLabel.textColor = .white
                blurView.contentView.addSubview(numberLabel)
                numberLabel.center(inView: blurView)
                
                // 单独处理图片加载
                guard let url = currentUser.imageURL else { return }
                numberImageView.sd_setImage(with: URL(string: url))
            }
        }
    }
    
    // 抽离公共的头像创建逻辑,减少重复代码
    private func createAvatarImageView() -> UIImageView {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFill
        imageView.layer.cornerRadius = size * 0.4
        imageView.layer.masksToBounds = true
        imageView.layer.borderWidth = 0.75
        imageView.layer.borderColor = UIColor.white.cgColor
        return imageView
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

适配CollectionView复用的使用方式

不需要每次都重新创建UserStack实例,在Cell复用周期内更新数据即可:

// Cell复用前清空旧数据
override func prepareForReuse() {
    super.prepareForReuse()
    userStack.userArray = []
}

// 配置Cell数据
func config(with users: [UserModel]) {
    userStack.userArray = users
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 21:30:05