Auto Layout in UICollectionViewCell not working
Well, I just looked on the iOS developer forums. Apparently, this is a bug with the iOS 8 SDK running on iOS 7 devices. The workaround is to add the following to your subclass of UICollectionViewCell:
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
self.contentView.frame = bounds;
}
override var bounds: CGRect {
didSet {
contentView.frame = bounds
}
}
UICollectionViewCell with AutoLayout not working in iOS 10
Well it's not really a solution, but the TTTAttributedLabel
does some black magic that causes the AutoLayout not to work. So for me, I changed the TTTAttributedLabel
to UILabel
and it works fine.
FYI I posted similar question on SnapKit Github issues; credits to robertjpayne there for the hint (https://github.com/SnapKit/SnapKit/issues/261)
UICollectionView Self Sizing Cells with Auto Layout
This answer is outdated from iOS 14 with the addition of compositional layouts. Please consider updating the new API
Updated for Swift 5
preferredLayoutAttributesFittingAttributes
renamed to preferredLayoutAttributesFitting
and use auto sizing
Updated for Swift 4
systemLayoutSizeFittingSize
renamed to systemLayoutSizeFitting
Updated for iOS 9
After seeing my GitHub solution break under iOS 9 I finally got the time to investigate the issue fully. I have now updated the repo to include several examples of different configurations for self sizing cells. My conclusion is that self sizing cells are great in theory but messy in practice. A word of caution when proceeding with self sizing cells.
TL;DR
Check out my GitHub project
Self sizing cells are only supported with flow layout so make sure thats what you are using.
There are two things you need to setup for self sizing cells to work.
#1. Set estimatedItemSize
on UICollectionViewFlowLayout
Flow layout will become dynamic in nature once you set the estimatedItemSize
property.
self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
#2. Add support for sizing on your cell subclass
This comes in 2 flavours; Auto-Layout or custom override of preferredLayoutAttributesFittingAttributes
.
Create and configure cells with Auto Layout
I won't go to in to detail about this as there's a brilliant SO post about configuring constraints for a cell. Just be wary that Xcode 6 broke a bunch of stuff with iOS 7 so, if you support iOS 7, you will need to do stuff like ensure the autoresizingMask is set on the cell's contentView and that the contentView's bounds is set as the cell's bounds when the cell is loaded (i.e. awakeFromNib
).
Things you do need to be aware of is that your cell needs to be more seriously constrained than a Table View Cell. For instance, if you want your width to be dynamic then your cell needs a height constraint. Likewise, if you want the height to be dynamic then you will need a width constraint to your cell.
Implement preferredLayoutAttributesFittingAttributes
in your custom cell
When this function is called your view has already been configured with content (i.e. cellForItem
has been called). Assuming your constraints have been appropriately set you could have an implementation like this:
//forces the system to do one layout pass
var isHeightCalculated: Bool = false
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
//Exhibit A - We need to cache our calculation to prevent a crash.
if !isHeightCalculated {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
newFrame.size.width = CGFloat(ceilf(Float(size.width)))
layoutAttributes.frame = newFrame
isHeightCalculated = true
}
return layoutAttributes
}
NOTE On iOS 9 the behaviour changed a bit that could cause crashes on your implementation if you are not careful (See more here). When you implement preferredLayoutAttributesFittingAttributes
you need to ensure that you only change the frame of your layout attributes once. If you don't do this the layout will call your implementation indefinitely and eventually crash. One solution is to cache the calculated size in your cell and invalidate this anytime you reuse the cell or change its content as I have done with the isHeightCalculated
property.
Experience your layout
At this point you should have 'functioning' dynamic cells in your collectionView. I haven't yet found the out-of-the box solution sufficient during my tests so feel free to comment if you have. It still feels like UITableView
wins the battle for dynamic sizing IMHO.
##Caveats
Be very mindful that if you are using prototype cells to calculate the estimatedItemSize - this will break if your XIB uses size classes. The reason for this is that when you load your cell from a XIB its size class will be configured with Undefined
. This will only be broken on iOS 8 and up since on iOS 7 the size class will be loaded based on the device (iPad = Regular-Any, iPhone = Compact-Any). You can either set the estimatedItemSize without loading the XIB, or you can load the cell from the XIB, add it to the collectionView (this will set the traitCollection), perform the layout, and then remove it from the superview. Alternatively you could also make your cell override the traitCollection
getter and return the appropriate traits. It's up to you.
Let me know if I missed anything, hope I helped and good luck coding
UICollectionViewCell autolayout installed views not working
This is an XCode bug with how it views the auto layout constraints. The work around is to be extra verbose with the constraints and untick the default 'installed' check box.
The constraints are now
I have filed a radar: https://openradar.appspot.com/radar?id=5056687413133312
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.
Programatically constraint doesn't work inside UICollectionViewCell Swift
This Solve it just needs to set
view.translatesAutoresizingMaskIntoConstraints = false
not The contenView
override func awakeFromNib() {
super.awakeFromNib()
let view = UIView()
view.frame = CGRect.zero
view.backgroundColor = UIColor.black
self.contentView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 0),
view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: 0),
view.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: 25),
view.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 25),
])
}
Related Topics
Steps to Upload an iPhone Application to the Appstore
Instead of Push Segue How to Replace View Controller (Or Remove from Navigation Stack)
How to Open Mail App from Swift
Xcode 8 Build Crash on iOS 9.2 and Below
How to Keep Uitableview Contentoffset After Calling -Reloaddata
Uitableview with Static Cells Does Not Appear
Real Time Blur Effect for Navigation Bar
iOS 8 Swift - Tableview with Embedded Collectionview
How to Access My Viewcontroller from My Appdelegate? iOS
Swift Default Alertviewcontroller Breaking Constraints
How to Enable Back/Left Swipe Gesture in Uinavigationcontroller After Setting Leftbarbuttonitem
Identify New iPhone Model on Xcode (5, 5C, 5S)
How to Reload Tableview from Another View Controller in Swift
Getting Device Orientation in Swift
Set Dimensions for Uiimagepickercontroller "Move and Scale" Cropbox
Module Compiled with Swift 4.0 Cannot Be Imported in Swift 4.0.1