UICollectionView flowLayout not wrapping cells correctly
There is a bug in UICollectionViewFlowLayout's implementation of layoutAttributesForElementsInRect that causes it to return TWO attribute objects for a single cell in certain cases involving section insets. One of the returned attribute objects is invalid (outside the bounds of the collection view) and the other is valid. Below is a subclass of UICollectionViewFlowLayout that fixes the problem by excluding cells outside of the collection view's bounds.
// NDCollectionViewFlowLayout.h
@interface NDCollectionViewFlowLayout : UICollectionViewFlowLayout
@end
// NDCollectionViewFlowLayout.m
#import "NDCollectionViewFlowLayout.h"
@implementation NDCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count];
for (UICollectionViewLayoutAttributes *attribute in attributes) {
if ((attribute.frame.origin.x + attribute.frame.size.width <= self.collectionViewContentSize.width) &&
(attribute.frame.origin.y + attribute.frame.size.height <= self.collectionViewContentSize.height)) {
[newAttributes addObject:attribute];
}
}
return newAttributes;
}
@end
See this.
Other answers suggest returning YES from shouldInvalidateLayoutForBoundsChange, but this causes unnecessary recomputations and doesn't even completely solve the problem.
My solution completely solves the bug and shouldn't cause any problems when Apple fixes the root cause.
CollectionView FlowLayout custom cell rendering issues
Thanks to some help from @nemecek_filip at the HWS forums, I got it solved!
Different approach to the gradient using a custom gradient view!
Hereby the changed and working code:
collectionView cell:
//
// NormalProjectCell.swift
//
import UIKit
class NormalProjectCell: UICollectionViewCell, SelfConfiguringProjectCell {
//MARK: - Properties
let titleLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .title3), andColor: .label)
let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .caption1), andColor: .secondaryLabel)
let imageView = ProjectImageView(frame: .zero)
var stackView = UIStackView()
var backgroundMaskedView = GradientView()
//MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 35
let seperator = Separator(frame: .zero)
stackView = UIStackView(arrangedSubviews: [seperator, titleLabel, lastEditedLabel, imageView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.spacing = 5
stackView.setCustomSpacing(10, after: lastEditedLabel)
stackView.insertSubview(backgroundMaskedView, at: 0)
contentView.addSubview(stackView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
NSLayoutConstraint.activate([
titleLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 20),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
backgroundMaskedView.translatesAutoresizingMaskIntoConstraints = false
backgroundMaskedView.pinToEdges(of: stackView)
}
//MARK: - Configure
func configure(with project: ProjectsController.Project) {
titleLabel.text = project.title
lastEditedLabel.text = project.lastEdited.customMediumToString
imageView.image = Bundle.getProjectImage(project: project)
}
}
And the GradientView:
//
// GradientView.swift
//
import UIKit
class GradientView: UIView {
var topColor: UIColor = UIColor.tertiarySystemBackground
var bottomColor: UIColor = UIColor.systemPurple
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
(layer as! CAGradientLayer).colors = [topColor.cgColor, bottomColor.cgColor]
(layer as! CAGradientLayer).locations = [0.0, 0.40]
}
}
UICollectionView does not return the cells correctly
Maybe...
1. NSDate *date = [calendar dateFromComponents:components];
2. NSDateComponents *compWeek = [calendar components:NSCalendarUnitWeekday fromDate:date];
3.
4. NSInteger weekDay = [compWeek weekday];
5.
6. /******* Numero di giorni del mese in corso *******/
7. /*************************************************/
8. NSInteger numberOfDay = [KPSmallCalendarDayData dayOfMonthFromDate:[NSDate date]];
It looks like you want to use date
from line 1, but you are passing a new date
object in line 8 ([NSDate date]
which == today). Should line 8 be:
NSInteger numberOfDay = [KPSmallCalendarDayData dayOfMonthFromDate:date];
In iOS 12, when does the UICollectionView layout cells, use autolayout in nib
For all solutions, note that there is no need to explicitly call reloadData
in viewDidLoad
: it will happen automatically.
Solution 1
Inspired by Samantha idea: invalidateLayout
asynchronously in viewDidLoad
.
override func viewDidLoad() {
super.viewDidLoad()
//[...]
for _ in 0 ..< 1000 {
array.append(randomKeyByBitLength(Int(arc4random_uniform(8)))!)
}
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
Solution 2
(imperfect, see DHennessy13 improvement on it)
Based on Peter Lapisu answer. invalidateLayout
in viewWillLayoutSubviews
.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
As noted by DHennessy13, this current solution with viewWillLayoutSubviews
is imperfect as it will invalidateLayout when rotating the screen.
You may follow DHennessy13 improvement regarding this solution.
Solution 3
Based on a combination of Tyler Sheaffer answer, Shawn Aukstak port to Swift and Samantha idea. Subclass your CollectionView to perform invalidateLayout
on layoutSubviews
.
class AutoLayoutCollectionView: UICollectionView {
private var shouldInvalidateLayout = false
override func layoutSubviews() {
super.layoutSubviews()
if shouldInvalidateLayout {
collectionViewLayout.invalidateLayout()
shouldInvalidateLayout = false
}
}
override func reloadData() {
shouldInvalidateLayout = true
super.reloadData()
}
}
This solution is elegant as it doesn't require to change your ViewController code. I've implemented it on branch AutoLayoutCollectionView of this sample project https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView.
Solution 4
Rewrite UICollectionViewCell default constraints. See Larry answer.
Solution 5
Implement collectionView(_:layout:sizeForItemAt:)
and return cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
. See matt answer.
Flowlayout left alignement in collection view
Fixed it with the TagCellLayout pod : https://github.com/riteshhgupta/TagCellLayout
UICollectionViewCells with UICollectionViewFlowLayout - strange layout happening
My answer is very specific and I am not sure it will help anyone.
The problem was that I had a constraint on the bottom of the grey view. After I changed this constraint to a less than or equal too then for some reason it worked.
Now I know this does not explain why it was not happening to every cell but it fixed my problem.
As such Harsh's answer might also be worth looking at if you have landed here after doing a search.
Edit: there also appears to be some bugs in the 6.0 UiCollectionView controller which seem to be fixed in 6.1
Related Topics
Using Ssl in an Iphone App - Export Compliance
How to Detect the End of Loading of Uitableview
How to Get a Plist as a Dictionary in Swift
How to Set the Width of a Cell in a Uitableview in Grouped Style
Getting iOS System Uptime, That Doesn't Pause When Asleep
How to Test Apple Push Notification Service Without an Iphone
Can Afnetworking Return Data Synchronously (Inside a Block)
How to Iterate For Loop in Reverse Order in Swift
Getting All Cookies from Wkwebview
How to Crop a Uiimageview to a New Uiimage in 'Aspect Fill' Mode
Swift - Stored Values Order Is Completely Changed in Dictionary
Add Placeholder Text Inside Uitextview in Swift
Programmatically Create a Uiview With Color Gradient
How to Programmatically Connect to a Wifi Network Given the Ssid and Password
Enumerate All Keychain Items in My iOS Application