Swift实现UICollectionView所有设备每行精准显示3个Cell
问题
想要实现一个图片UICollectionView,使每行显示3个带间距的单元格。当前代码在iPhone 11及以上设备可正常运行,但在iPhone XS等更早设备上失效,附上对应设备的截图。需要修正代码,实现所有iOS设备上每行精准显示3个Cell。
原代码:
func setupCollectioView(){ guard let collectionView = gridPhotosCollectionview, let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } flowLayout.minimumInteritemSpacing = 2 flowLayout.minimumLineSpacing = 2 flowLayout.sectionInset = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) gridPhotosCollectionview.delegate = self gridPhotosCollectionview.dataSource = self } func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return images.count } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let noOfCellsInRow = 3 let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout let totalSpace = flowLayout.sectionInset.left + flowLayout.sectionInset.right + (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1)) let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(noOfCellsInRow)) return CGSize(width: size, height: size) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { let totalCount = selectedImages.count + selectedPostDetailImages.count if totalCount == 1 { let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: collectionView.frame.width - flowLayout.itemSize.width) } return UIEdgeInsets.zero } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 10 }
修正方案
问题根源
- 尺寸精度丢失:原代码将计算后的尺寸转为
Int截断小数部分,在屏幕宽度无法被完美整除的旧设备上,累计误差会导致布局错乱。 - 布局逻辑冲突:同时在
setupCollectionView和代理方法中设置间距、内边距,旧系统中代理方法的优先级与新系统存在差异,导致布局混乱。 - 依赖未初始化值:
insetForSectionAt中直接使用flowLayout.itemSize,此时该值可能还未完成计算,导致单Cell布局错误。
修正后的完整代码
func setupCollectionView(){ guard let collectionView = gridPhotosCollectionview, let _ = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } gridPhotosCollectionview.delegate = self gridPhotosCollectionview.dataSource = self // 强制刷新确保布局参数生效 collectionView.reloadData() } func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return images.count } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let noOfCellsInRow = 3 let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout // 使用collectionView的frame宽度,避免bounds未更新的问题 let availableWidth = collectionView.frame.width let totalSpacing = flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1) let totalInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right let totalSpace = totalInsets + totalSpacing // 保留浮点精度,不转为Int let size = (availableWidth - totalSpace) / CGFloat(noOfCellsInRow) return CGSize(width: size, height: size) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { let totalCount = selectedImages.count + selectedPostDetailImages.count if totalCount == 1 { let noOfCellsInRow = 3 let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout // 实时计算item宽度,避免依赖未初始化的值 let availableWidth = collectionView.frame.width let totalSpacing = flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1) let totalInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right let totalSpace = totalInsets + totalSpacing let itemWidth = (availableWidth - totalSpace) / CGFloat(noOfCellsInRow) return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: availableWidth - itemWidth) } // 统一返回预设内边距,避免与setup逻辑冲突 return UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 2 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 2 }
关键修改点
- 保留浮点尺寸:删除
Int()转换,确保尺寸计算精度,避免累计误差导致布局错位。 - 统一布局控制:将所有布局参数(间距、内边距)移至代理方法中设置,消除
setupCollectionView与代理方法的逻辑冲突。 - 实时计算单Cell内边距:在
insetForSectionAt中实时计算item宽度,不再依赖未初始化的flowLayout.itemSize。 - 强制刷新布局:在
setupCollectionView末尾调用reloadData(),确保布局参数生效后立即刷新界面。
内容的提问来源于stack exchange,提问作者Sham Dhiman
相关产品推荐
相关产品推荐

