Auto Layout in Uicollectionviewcell Not Working

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

Sample Image

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



Leave a reply



Submit