iPhone SE模拟器CollectionView异常,iPhone 11正常的问题求助
Hey there! Let's figure out why you're getting that weird blank cell on smaller devices like the iPhone SE.
What's Causing the Issue?
Looking at your code, I see two main potential culprits:
- One-time item size calculation: Your
setupCollectionViewItemSize()only calculates the item width once (whencollectionViewFlowLayoutis nil). Even though this runs on first launch for each device, if the collection view's final bounds aren't set yet whenviewWillLayoutSubviewsfires, you might be using an incorrect width value. - Floating-point precision issues: When you calculate the item width, dividing the collection view's width by 3 might result in a fractional value. Adding up 3 of those plus the inter-item spacing could push the total width just over the collection view's bounds, forcing the third cell to wrap to the next row—leaving a blank spot where it should be.
Fixes to Try
Let's go through two solid solutions, starting with the simplest tweak to your existing code, then a more robust approach using collection view delegates.
Solution 1: Update Item Size Every Time Layout Changes
Modify your setupCollectionViewItemSize() to recalculate the item size on every layout update, not just when the flow layout is first created. We'll also use floor() to avoid fractional widths that cause overflow:
private func setupCollectionViewItemSize(){ let numberOfItemsPerRow: CGFloat = 3 let lineSpacing: CGFloat = 5 let interItemSpacing: CGFloat = 5 // Use bounds.width instead of frame.width—it's more reliable for layout calculations let availableWidth = collectionView.bounds.width - (numberOfItemsPerRow - 1) * interItemSpacing // Floor the width to eliminate fractional values that cause layout overflow let width = floor(availableWidth / numberOfItemsPerRow) let height = width if collectionViewFlowLayout == nil { collectionViewFlowLayout = UICollectionViewFlowLayout() collectionViewFlowLayout.sectionInset = UIEdgeInsets.zero collectionViewFlowLayout.scrollDirection = .vertical collectionViewFlowLayout.minimumLineSpacing = lineSpacing collectionViewFlowLayout.minimumInteritemSpacing = interItemSpacing collectionView.setCollectionViewLayout(collectionViewFlowLayout, animated: false) } // Update the item size EVERY time this method runs collectionViewFlowLayout.itemSize = CGSize(width: width, height: height) }
Solution 2: Use UICollectionViewDelegateFlowLayout (More Robust)
For better control and adherence to collection view best practices, use the delegate methods to dynamically return item sizes and spacing. This way, the system asks for these values whenever it needs to layout the collection view:
First, make sure your view controller conforms to UICollectionViewDelegateFlowLayout (you probably already conform to UICollectionViewDelegate and UICollectionViewDataSource, so just add it):
class YourViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { // ... your existing code ... }
Then remove the setupCollectionViewItemSize() method and the collectionViewFlowLayout property entirely, and add these delegate methods:
// Return the size for each cell func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let numberOfItemsPerRow: CGFloat = 3 let interItemSpacing: CGFloat = 5 let availableWidth = collectionView.bounds.width - (numberOfItemsPerRow - 1) * interItemSpacing let width = floor(availableWidth / numberOfItemsPerRow) return CGSize(width: width, height: width) } // Set inter-item spacing func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 5 } // Set line spacing (space between rows) func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 5 }
And update your setupCollectionView() to remove any flow layout setup (since the delegate will handle it):
private func setupCollectionView(){ collectionView.delegate = self collectionView.dataSource = self let nib = UINib(nibName: "ItemCollectionViewCell", bundle: nil) collectionView.register(nib, forCellWithReuseIdentifier: cellIdentifier) // Optional: If you want to reset any default layout settings if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.sectionInset = UIEdgeInsets.zero flowLayout.scrollDirection = .vertical } }
Why These Work
- Recalculating on every layout update: Ensures you're always using the latest collection view bounds, which is crucial if the view size changes (like rotation) or if the initial frame isn't final when
viewWillLayoutSubviewsfirst runs. - Flooring the width: Eliminates tiny fractional values that add up to make the total width exceed the collection view's bounds, which was likely pushing your third cell to the next row.
Either solution should fix that blank cell issue on smaller devices like the iPhone SE. Give them a try!
内容的提问来源于stack exchange,提问作者Part_Time_Nerd

