UIKit:UITableViewCell内UIStackView实现图片重叠排列求助
UITableViewCell实现用户图片水平堆叠重叠效果问题
需求
使用UIKit开发应用,需在UITableViewCell中展示一组用户图片,要求图片以水平堆叠且互相重叠的形式呈现。
效果对比
期望效果:
当前效果:
问题代码
import UIKit import SDWebImage class CountryTableCell: UITableViewCell { static let reuseIdentifier = "CountryTableCell" //MARK: - Propeties var viewModel: CountryViewModel? { didSet { viewModel?.delegate = self setUpSpace(viewModel) } } //MARK: - SubViews private let container: UIView = { let view = UIView() view.withSize(CGSize(width: Dimensions.maxSafeWidth, height: 150)) view.backgroundColor = .secondary_background view.layer.cornerRadius = 20 return view }() private let adminsContainer: UIView = { let view = UIView() view.withSize(CGSize(width: (Dimensions.maxSafeWidth - 32), height: Dimensions.image.mediumHeigthWithOverlay)) view.backgroundColor = .secondary_background return view }() private let adminsStack: UIStackView = { let stack = UIStackView() stack.axis = .horizontal stack.alignment = .leading stack.spacing = -10 stack.sizeToFit() stack.distribution = .fillEqually stack.withSize(CGSize(width: Dimensions.image.mediumHeigthWithOverlay, height: Dimensions.image.mediumHeigthWithOverlay)) return stack }() private let nameLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title2).pointSize, weight: .bold) label.textColor = .primary_label label.textAlignment = .left label.sizeToFit() label.numberOfLines = 0 return label }() var headerView: UIImageView = { let iv = UIImageView() iv.contentMode = .scaleAspectFill iv.clipsToBounds = true iv.withSize(CGSize(width: Dimensions.image.headerSizeForCell.width, height: ((Dimensions.image.mediumHeigth/2) + Padding.horizontal))) iv.backgroundColor = .secondary_background iv.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] iv.layer.cornerRadius = 20 return iv }() //MARK: - Init override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) addSubview(container) container.center(inView: self) addSubview(headerView) headerView.anchor(top: container.topAnchor, left: container.leftAnchor, right: container.rightAnchor) addSubview(nameLabel) nameLabel.anchor(top: headerView.bottomAnchor, left: container.leftAnchor, right: container.rightAnchor, paddingLeft: Padding.horizontal, paddingRight: Padding.horizontal) addSubview(adminsContainer) adminsContainer.anchor(left: container.leftAnchor, bottom: headerView.bottomAnchor, right: container.rightAnchor ,paddingLeft: Padding.horizontal, paddingBottom: -(Dimensions.image.mediumHeigth/2), paddingRight: Padding.horizontal) bringSubviewToFront(adminsContainer) adminsContainer.backgroundColor = .clear.withAlphaComponent(0.0) adminsContainer.addSubview(adminsStack) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } //MARK: - Lifecycle override func willRemoveSubview(_ subview: UIView) { } //MARK: - Helpers private func setUpSpace(_ viewModel: CountryViewModel?) { guard let viewModel = viewModel else {return} if let url = viewModel.country.headerImage?.url { headerView.sd_setImage(with: URL(string: url)) } nameLabel.text = viewModel.space.name } } // MARK: Extension extension SpaceTableCell: CountryViewModelDelegate { func didFetchAdmin(_ admin: User) { if let admins = viewModel?.admins.count { if admins == 1 { adminsStack.withSize(CGSize(width: Dimensions.image.mediumHeigthWithOverlay, height: Dimensions.image.mediumHeigthWithOverlay)) } else if admins == 2 { adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*2) - 10), height: Dimensions.image.mediumHeigthWithOverlay)) } else if admins == 3 { adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*3) - 20), height: Dimensions.image.mediumHeigthWithOverlay)) } else if admins == 4 { adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*4) - 30), height: Dimensions.image.mediumHeigthWithOverlay)) } else if admins == 5 { adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*5) - 40), height: Dimensions.image.mediumHeigthWithOverlay)) } } let image = UserImageView(height: Dimensions.image.mediumHeigth) image.sd_setImage(with: admin.profileImageURL) adminsStack.addArrangedSubview(image) } } class UserImageView: UIImageView { //MARK: - Propeties let selectedHeigth: CGFloat init(height: CGFloat) { self.selectedHeigth = height super.init(frame: CGRect(x: 0, y: 0, width: selectedHeigth, height: selectedHeigth)) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() contentMode = .scaleAspectFill clipsToBounds = true backgroundColor = .secondary_background layer.cornerRadius = Dimensions.userImageCornerRadious(selectedHeigth) } }
解决方案
1. 修正StackView核心配置
fillEqually会强制拉伸子视图导致图片变形,固定宽度也会限制堆叠效果,修改adminsStack初始化代码:
private let adminsStack: UIStackView = { let stack = UIStackView() stack.axis = .horizontal stack.alignment = .leading stack.spacing = -10 // 保留负间距实现重叠 stack.distribution = .fill // 改为fill,让子视图保持原尺寸 return stack }()
2. 完善StackView布局约束
在init方法中给adminsStack添加明确约束,确保它在容器内靠左对齐、高度匹配:
adminsContainer.addSubview(adminsStack) // 添加以下约束 adminsStack.anchor(top: adminsContainer.topAnchor, left: adminsContainer.leftAnchor, bottom: adminsContainer.bottomAnchor)
3. 移除冗余宽度计算逻辑
删除didFetchAdmin中手动计算StackView宽度的代码块,StackView会根据子视图尺寸和负间距自动调整宽度。
4. 增加Cell复用清理
在setUpSpace方法开头添加清理逻辑,避免Cell复用时图片重复叠加:
private func setUpSpace(_ viewModel: CountryViewModel?) { guard let viewModel = viewModel else {return} // 清理旧的图片视图 adminsStack.arrangedSubviews.forEach { $0.removeFromSuperview() } if let url = viewModel.country.headerImage?.url { headerView.sd_setImage(with: URL(string: url)) } nameLabel.text = viewModel.space.name }
5. 优化后的didFetchAdmin方法
func didFetchAdmin(_ admin: User) { let image = UserImageView(height: Dimensions.image.mediumHeigth) image.sd_setImage(with: admin.profileImageURL) adminsStack.addArrangedSubview(image) }
以上修改完成后,图片会以水平堆叠、互相重叠的形式正常显示,符合期望效果。
内容的提问来源于stack exchange,提问作者SamP
相关产品推荐
相关产品推荐

