Dynamic UIcollectionviewcell Width with Respect to Frame Width and Aspect Ratio

Dynamic UICollectionViewCell width with respect to frame width and aspect ratio

All you need to specify is the smallest size a cell should be. Cells will be at least as wide (and high) as that size, and possibly larger.

Approach:

Here's a method which will calculate the appropriate cell size based on the width of its collectionView:

// These variables are set in the Storyboard, but shown here for clarity
flowLayout.minimumInteritemSpacing = 10
flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

let minimumSize: CGFloat = 90.0 // A cell's width or height won't ever be smaller than this

func cellWidthForViewWidth(viewWidth: CGFloat) -> CGFloat {
// Determine the largest number of cells that could possibly fit in a row, based on the cell's minimum size
var numberOfCellsPerRow = Int(viewWidth / minimumSize)

// Adjust for interitem spacing and section insets
let availableWidth = viewWidth - flowLayout.sectionInset.left - flowLayout.sectionInset.right - flowLayout.minimumInteritemSpacing * CGFloat(numberOfCellsPerRow - 1)
numberOfCellsPerRow = Int(availableWidth / minimumSize)

return availableWidth / CGFloat(numberOfCellsPerRow) // Make this an integral width if desired
}

Handling a non 1:1 aspect ratio:

If your aspect ratio happened to be different from 1:1, you would simply determine the dynamic height by dividing the calculated width by the aspect ratio.

let aspectRatio: CGFloat = 3.0/2.0
let cellWidth = cellWidthForViewWidth(CGRectGetWidth(collectionView.frame))
let cellHeight = cellWidth / aspectRatio

Setting the item size:

Since your aspect ratio is 1:1, we only need to calculate the width for a cell, then use that value for both width and height.

Set the actual item size based on the calculated dynamic size:

let actualCellSize = cellWidthForViewWidth(CGRectGetWidth(collectionView.frame))
flowLayout.itemSize = CGSize(width: actualCellSize, height: actualCellSize)

Sample results:

This will give you an idea what size a cell would be, based on various container widths:

print(cellWidthForViewWidth(320.0))  //  93,  3 cells per row
print(cellWidthForViewWidth(400.0)) // 116, 3 cells per row
print(cellWidthForViewWidth(640.0)) // 93, 6 cells per row
print(cellWidthForViewWidth(800.0)) // 101, 7 cells per row
print(cellWidthForViewWidth(1024.0)) // 90, 10 cells per row

How to set UICollectionViewCell Width and Height programmatically

Use this method to set custom cell height width.

Make sure to add this protocols

UICollectionViewDelegate

UICollectionViewDataSource

UICollectionViewDelegateFlowLayout

If you are using swift 5 or xcode 11 and later you need to set Estimate Size to none using storyboard in order to make it work properly. If you will not set that than below code will not work as expected.

Sample Image

Swift 4 or Later

extension YourViewController: UICollectionViewDelegate {
//Write Delegate Code Here
}

extension YourViewController: UICollectionViewDataSource {
//Write DataSource Code Here
}

extension YourViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: screenWidth, height: screenWidth)
}
}

Objective-C

@interface YourViewController : UIViewController<UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout>

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(CGRectGetWidth(collectionView.frame), (CGRectGetHeight(collectionView.frame)));
}

UICollectionView, full width cells, allow autolayout dynamic height?

1. Solution for iOS 13+

With Swift 5.1 and iOS 13, you can use Compositional Layout objects in order to solve your problem.

The following complete sample code shows how to display multiline UILabel inside full-width UICollectionViewCell:

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

let items = [
[
"Lorem ipsum dolor sit amet.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
]
]

override func viewDidLoad() {
super.viewDidLoad()

let size = NSCollectionLayoutSize(
widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
heightDimension: NSCollectionLayoutDimension.estimated(44)
)
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
section.interGroupSpacing = 10

let headerFooterSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(40)
)
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: "SectionHeaderElementKind",
alignment: .top
)
section.boundarySupplementaryItems = [sectionHeader]

let layout = UICollectionViewCompositionalLayout(section: section)
collectionView.collectionViewLayout = layout
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
}

override func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.count
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].count
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label.text = items[indexPath.section][indexPath.row]
return cell
}

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
headerView.label.text = "Header"
return headerView
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { context in
self.collectionView.collectionViewLayout.invalidateLayout()
}, completion: nil)
}

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .magenta

addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)

label.numberOfLines = 0
backgroundColor = .orange
contentView.addSubview(label)

label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

}

Expected display:

Sample Image


2. Solution for iOS 11+

With Swift 5.1 and iOS 11, you can subclass UICollectionViewFlowLayout and set its estimatedItemSize property to UICollectionViewFlowLayout.automaticSize (this tells the system that you want to deal with autoresizing UICollectionViewCells). You'll then have to override layoutAttributesForElements(in:) and layoutAttributesForItem(at:) in order to set cells width. Lastly, you'll have to override your cell's preferredLayoutAttributesFitting(_:) method and compute its height.

The following complete code shows how to display multiline UILabel inside full-width UIcollectionViewCell (constrained by UICollectionView's safe area and UICollectionViewFlowLayout's insets):

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

let items = [
[
"Lorem ipsum dolor sit amet.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
]
]
let customFlowLayout = CustomFlowLayout()

override func viewDidLoad() {
super.viewDidLoad()

customFlowLayout.sectionInsetReference = .fromContentInset // .fromContentInset is default
customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
customFlowLayout.minimumInteritemSpacing = 10
customFlowLayout.minimumLineSpacing = 10
customFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
customFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)

collectionView.collectionViewLayout = customFlowLayout
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
}

override func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.count
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].count
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label.text = items[indexPath.section][indexPath.row]
return cell
}

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
headerView.label.text = "Header"
return headerView
}

}

CustomFlowLayout.swift

import UIKit

final class CustomFlowLayout: UICollectionViewFlowLayout {

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
fatalError()
}
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}

layoutAttributes.frame.origin.x = sectionInset.left
layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
return layoutAttributes
}

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .magenta

addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)

label.numberOfLines = 0
backgroundColor = .orange
contentView.addSubview(label)

label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
layoutIfNeeded()
layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}

}

Here are some alternative implementations for preferredLayoutAttributesFitting(_:):

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
label.preferredMaxLayoutWidth = layoutAttributes.frame.width
layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
return layoutAttributes
}

Expected display:

Sample Image

UICollectionViewCell dynamic sizing for 4.4 and 5.5 inch

If you want your cells to adjust their width, you'll need to calculate the flow layout's itemSize based on the view width.

  • If all your cells will be the same size, you can set the flow layout's itemSize property:

    #define kCellsPerRow 2

    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout;
    CGFloat availableWidthForCells = CGRectGetWidth(self.collectionView.frame) - flowLayout.sectionInset.left - flowLayout.sectionInset.right - flowLayout.minimumInteritemSpacing * (kCellsPerRow - 1);
    CGFloat cellWidth = availableWidthForCells / kCellsPerRow;
    flowLayout.itemSize = CGSizeMake(cellWidth, flowLayout.itemSize.height);

    If you want to dynamically vary the number of cells per row, I've provided those details in a separate answer.

  • If you need to calculate per-cell sizes, you can do it in the collectionView:layout:sizeForItemAtIndexPath: delegate.

Recalculate the itemSize when the device orientation changes and update the layout:

  • Redraw layout without animation:

    [self.collectionView.collectionViewLayout invalidateLayout]

  • Animate layout changes:

    [self.collectionView performBatchUpdates:nil completion:nil]

How to increase the UICollectionview Cell Size equal to Screen Size in swift 4.2?

You need to change your cell size to fit the collectionview. Add this in viewDidLoad

if let flow = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flow.itemSize = self.collectionView.frame.size
}

How to resize the collection view cells according to device screen size?

Implement sizeForItemAt method according to the view's frame size

Swift

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

let height = view.frame.size.height
let width = view.frame.size.width
// in case you you want the cell to be 40% of your controllers view
return CGSize(width: width * 0.4, height: height * 0.4)
}

Objective C

- (CGSize)collectionView:(UICollectionView *)collectionView 
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {

CGFloat height = self.view.frame.size.height;
CGFloat width = self.view.frame.size.width;
// in case you you want the cell to be 40% of your controllers view
return CGSizeMake(width * 0.4, height * 0.4);
}


Related Topics



Leave a reply



Submit