I am trying to get 100% of the width of my collectionView with Swift 3 but I am confused. The method I am trying does not work. So far I have:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 25)
}
Any ideas?
I think you are trying to accomplish this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width, height: 25)
}
collectionView.frame.size.width will get the width of the collectionView.
Note: The the parameters passed in are points, not percentages. So what you are returning is 100 points x 25 points. Thanks #Randy for the reminder!
I have a custom UICollectionViewFlowLayout subclass that I use. Note that you probably want to take into account insets, etc.
class AutoSizingCollectionViewFlowLayout: UICollectionViewFlowLayout {
#IBInspectable var height: CGFloat = 0
#IBInspectable var spacing: CGFloat = 0
override func prepareLayout() {
let containerBounds = (self.collectionView?.window != nil) ? self.collectionView?.bounds : UIScreen.mainScreen().bounds
var size = self.itemSize
size.width = CGRectGetWidth(containerBounds!)
size.width -= (self.sectionInset.left + self.sectionInset.right)
size.height = height
self.itemSize = size
self.minimumLineSpacing = spacing
super.prepareLayout()
}
}
Use this code in Swift 4:
var collectionWidth = collectionView.frame.size.width
if #available(iOS 11.0, *) {
collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
}
The new iPhone nudge needs the safe area to be checked
Related
Following is my code which is working fine if numberOfItemsInSection is greater than 1 but if numberOfItemsInSection is equal to 1 it should display single cell but it is not happening, for numberOfItemsInSection equals 1 noting is displayed and cellForItemAt is not called
Also initial numberOfItemsInSection is Zero after making api call and receiving data it becomes one and I call following code as well
tabsCollectionView.reloadData()
Other code
tabsCollectionView.delegate = self
tabsCollectionView.dataSource = self
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuResult?.foodMenuItems?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = tabsCollectionView.dequeueReusableCell(withReuseIdentifier: "foodTabsCollectionViewCellID", for: indexPath) as! FoodTabsCollectionViewCell
cell.foodMenuItem = menuResult?.foodMenuItems?[indexPath.row]
cell.setUI()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
following code related sizing in side viewDidLoad()
if let flowLayout = tabsCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 200, height: 70)
flowLayout.itemSize = UICollectionViewFlowLayout.automaticSize
tabsCollectionView.collectionViewLayout = flowLayout
}
sizeForItemAt method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height = collectionView.frame.height
var width = collectionView.frame.width
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
width = flowLayout.estimatedItemSize.width
height = flowLayout.estimatedItemSize.height
}
return CGSize(width: width, height: height)
}
Following is code inside FoodTabsCollectionViewCell
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.width = ceil(size.width)
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
layoutIfNeeded()
return layoutAttributes
}
I had the same problem when I tried to set a flexible width to a cell containing a UILabel with variable width (actually the size was related to the width of the text of the UILabel)
So initially I was using this approach (THIS was causing the problems):
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize <--- THIS
layout.itemSize.height = 70 <-- THIS
Then I removed the automaticSize (and itemSize.height) as below:
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
And it worked.
To make sure that I had flexible cell width I used this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let dummyCell = MyCell(frame: .init(x: 0, y: 0, width: frame.width, height: 40))
dummyCell.name = users[indexPath.item].name
// 3. Force the view to update its layout
dummyCell.layoutIfNeeded()
// 4. Calculate the estimated size
let estimatedSize = dummyCell.systemLayoutSizeFitting(.init(width: frame.width, height: 40))
return CGSize(width: estimatedSize.width, height: 40)
}
The problem will happen when the collectionView scroll direction is horizontal and the height of collectionView is less than 50. I had the same problem and I increased the collectionView height to 50 then the problem solved.
I've been working on with a custom UICollectionViewFlowLayout to adjust the spaces between the cells so I can get a nice flow in my collectionView. But with my current code I can't figure out how I can adjust the cell sizes without "breaking" rows count. Which makes some cells think they can fit a row but they can't.
My custom flowlayout
class UserProfileTagsFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributesForElementsInRect = super.layoutAttributesForElements(in: rect)
var newAttributesForElementsInRect = [UICollectionViewLayoutAttributes]()
var leftMargin: CGFloat = 0.0;
for attributes in attributesForElementsInRect! {
if (attributes.frame.origin.x == self.sectionInset.left) {
leftMargin = self.sectionInset.left
} else {
var newLeftAlignedFrame = attributes.frame
newLeftAlignedFrame.origin.x = leftMargin
attributes.frame = newLeftAlignedFrame
}
leftMargin += attributes.frame.size.width + 8 // Makes the space between cells
newAttributesForElementsInRect.append(attributes)
}
return newAttributesForElementsInRect
}
}
So in my ViewController I've extende
extension myViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let tag = tagsArray[indexPath.row]
let font = UIFont(name: "Raleway", size: 14)!
let size = tag.value.size(withAttributes: [NSAttributedString.Key.font: font])
let dynamicCellWidth = size.width
/*
The "+ 20" gives me the padding inside the cell
*/
return CGSize(width: dynamicCellWidth + 20, height: 35)
}
// Space between rows
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
// Space between cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
When I try this code out i'm getting the result down in the result-section on an iPhone X. You can see the tag "Machine Learning" jumps down to a new row, because it thinks it can fit the row above. If I remove the "padding" for the cell in sizeForItem function, then I won't have this problem. But it just looks awful.
So my question is: How can I use padding on my cells without breaking the flowLayout?
So far my current result looks like this:
iPhone X:
Maybe it will help you. I think you should check if you item stick out the collection view right inset.
In layoutAttributesForElements method check this:
let collectionViewWidth = self.collectionView?.frame.width - (self.sectionInset.right + self.sectionInset.left)
if (attributes.frame.origin.x + attributes.frame.size.width > collectionViewWidth) {
var newLeftAlignedFrame = attributes.frame
newLeftAlignedFrame.origin.x = self.sectionInset.left
attributes.frame = newLeftAlignedFrame
}
Updated my answer and it works for me, you can see it on screenshot
I have a horizontal scrolling collectionview.The cell contains a label.I want to adjust the cell width based on the content of the label.How can this be achieved??Please help me with this
instead of calling sizeforitem
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height : 100)
}
You can set layout properties to auto resizing according to label content
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.estimatedItemSize = CGSize(width: 100, height: 30)
Use this func to calculate the label width
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSFontAttributeName: font]
let size = self.size(attributes: fontAttributes)
return size.width
}
Use UICollectionViewDelegateFlowLayout to set size of collectionViewCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = //calculated width of label from func
return CGSize(width: width, height:requiredHeight)
}
I have a collection view with 6 items and want to display them in a 2 cells per row and 3 rows format. The following code achieves this (as taken from this question: Swift: Collection View not adjusting correctly to change in size) in iPhone format nicely.
However on the any iPad the views layout is correct initially but if the screen is rotated to landscape and then back to portrait then the layout does not fully fit within the view and requires horizontal scrolling to see the second cell (cells width has somehow increased meaning the second cell in each row is partially cut off).
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 5
flowLayout.minimumInteritemSpacing = 5
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
self.collectionView.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if UIDevice.current.orientation.isLandscape {
let totalWidth = collectionView.frame.width
let totalHeight = collectionView.frame.height
let heightOfCell = totalHeight / 2
let numberOfCellsPerRow = 1
let widthOfCell = CGFloat(Int(totalWidth) / numberOfCellsPerRow)
return CGSize(width: widthOfCell , height: heightOfCell)
} else {
let totalWidth = collectionView.frame.width
let totalHeight = collectionView.frame.height
let heightOfCell = (totalHeight / 3)
let numberOfCellsPerRow = 2
let widthOfCell = CGFloat(Int(totalWidth) / numberOfCellsPerRow)
return CGSize(width: widthOfCell , height: heightOfCell)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 5, 0, 5)
}
The problem here appears to be that, frame of the view is not updated when 'willRotate' is invoked. Instead try 'didrotate' or add the observer 'NSNotification.Name.UIDeviceOrientationDidChange' and invalidateLayout of collection inside the selector method. Don't forget to remove the observer on deinit().
I have a collection view controller and I set up it like this:
class r : UICollectionViewCell {
override var bounds: CGRect {
didSet {
contentView.frame = bounds
}
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "1", for: indexPath) as? r
cell.backgroundColor = UIColor.red
return cell
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
I want that the cell's width is equals to screen's width , for this reason in storyboard I edited cell's width:
but I have a problem :
if I execute it on iPhone 6 simulator I get this(and this is what I want to get on all devices) :
but If execute on iPad Pro I get this :
And I don't understand why.
Can you help me?
P.S I don't want to use tableview for some reasons
In iOS 10 there is a minor difference in function prototype:
Here is Swift3.0 Delegate method for sizeAtItemAtIndexPath:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let screenRect: CGRect = UIScreen.main.bounds
let screenWidth: CGFloat = screenRect.size.width
let screencellHeight: CGFloat = screenRect.size.height
let cellWidth: CGFloat = screenWidth / CGFloat(2)
let cellHeight:CGFloat = screencellHeight / CGFloat(10.0)
let size: CGSize = CGSize(width: cellWidth, height: cellHeight)
return size
}
If u use Constraint so first of all your cell to apply equal width constraint and also storyboard in you should check collection view setting in inspect editor for horizontal and vertical.proper all setting..
I assume you have set a fixed width for your cell somewhere. Maybe as constraint. Set it dynamically:
Conform to UICollectionViewDelegateFlowLayout
and override:
#objc(collectionView:layout:sizeForItemAtIndexPath:)
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let desiredNumberOfCollumns: CGFloat = 1
let width = collectionView.frame.width / desiredNumberOfCollumns
let height = 100 // your desired height
return CGSize(width: width, height: hight)
}
I think you are using constraints. If yes, then all you have to do is just fix the width of your cell in constraints.
Unless you didn't change that. No matter where you have set the width ind delegates.It will remain same.