自定义叠加布局UICollectionView滚动时单元格消失问题求助
解决UICollectionView滚动时单元格位置异常问题
问题根源
- 直接在
cellForItemAt中修改cell.frame会破坏UICollectionViewFlowLayout的自动布局逻辑,单元格复用后位置计算完全混乱 - 当前设置的负
minimumLineSpacing会导致组与组之间的间距计算错误,滚动时布局偏移加剧 - 手动计算的
rowOffset没有结合UICollectionView的滚动状态,无法适配滚动时的位置更新
解决方案
移除手动修改cell frame的代码,通过自定义UICollectionViewFlowLayout来精准控制每个单元格的位置,让布局逻辑完全由布局类管理,避免与UICollectionView的复用机制冲突。
修改后的完整代码
import UIKit class SpiralCollectionLayout: UICollectionViewFlowLayout { // 每组包含5个item private let itemsPerGroup = 5 // 每组的高度,基于collectionView宽度计算 private var groupHeight: CGFloat { guard let collectionView = collectionView else { return 0 } return collectionView.frame.width } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let originalAttributes = super.layoutAttributesForElements(in: rect) else { return nil } var adjustedAttributes = [UICollectionViewLayoutAttributes]() for attributes in originalAttributes { guard let copy = attributes.copy() as? UICollectionViewLayoutAttributes else { continue } adjustLayoutAttributes(copy) adjustedAttributes.append(copy) } return adjustedAttributes } private func adjustLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) { guard let collectionView = collectionView else { return } let itemIndex = attributes.indexPath.item let groupIndex = itemIndex / itemsPerGroup let itemInGroup = itemIndex % itemsPerGroup let groupYOffset = CGFloat(groupIndex) * groupHeight let width = collectionView.frame.width // 根据组内位置设置每个item的frame switch itemInGroup { case 0: attributes.frame = CGRect(x: 0, y: groupYOffset, width: width * 2/3, height: width * 1/3) case 1: attributes.frame = CGRect(x: width * 2/3, y: groupYOffset, width: width * 1/3, height: width * 2/3) case 2: attributes.frame = CGRect(x: 0, y: groupYOffset + width * 1/3, width: width * 1/3, height: width * 2/3) case 3: attributes.frame = CGRect(x: width * 1/3, y: groupYOffset + width * 2/3, width: width * 1/3, height: width * 1/3) case 4: attributes.frame = CGRect(x: width * 1/3, y: groupYOffset + width * 1/3, width: width * 2/3, height: width * 1/3) default: break } } override func collectionViewContentSize() -> CGSize { guard let collectionView = collectionView else { return .zero } let totalGroups = (collectionView.numberOfItems(inSection: 0) + itemsPerGroup - 1) / itemsPerGroup return CGSize(width: collectionView.frame.width, height: CGFloat(totalGroups) * groupHeight) } } class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate { var collectionView: UICollectionView! let colors = [UIColor.yellow, UIColor.red, UIColor.blue, UIColor.green, UIColor.purple, UIColor.systemRed, UIColor.systemMint, UIColor.systemBlue, UIColor.cyan, UIColor.gray, UIColor.systemTeal, UIColor.systemYellow] func generateRandomNumber(min: Int, max: Int) -> Int { return Int.random(in: min...max) } override func viewDidLoad() { super.viewDidLoad() // 使用自定义布局 let layout = SpiralCollectionLayout() layout.minimumInteritemSpacing = 0 layout.minimumLineSpacing = 0 layout.sectionInset = .zero collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout) collectionView.dataSource = self collectionView.delegate = self collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") collectionView.backgroundColor = .white view.addSubview(collectionView) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 300 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) let randomNumber = generateRandomNumber(min: 0, max: colors.count-1) cell.backgroundColor = colors[randomNumber] // 移除手动修改frame的代码 return cell } }
关键改动说明
- 自定义
SpiralCollectionLayout,重写layoutAttributesForElements(in:)和collectionViewContentSize()方法,完全接管布局计算 - 按每5个item为一组,计算每组的Y轴偏移,然后为组内每个item设置精准的frame
- 移除
cellForItemAt中所有手动修改frame的代码,避免干扰布局逻辑 - 自定义布局中直接计算每个item的尺寸和位置,无需再实现
UICollectionViewDelegateFlowLayout的sizeForItemAt方法
内容的提问来源于stack exchange,提问作者OnlyDans
相关产品推荐
相关产品推荐

