I have collection view in the storyboard. I have a part of cells in storyboard and part of cells I created programmatically. What should I do in sizeForItemAtIndexPath method? Which value should I return? For my programmatically created cells I return size for him. But I don't want copy size from storyboard for cells from storyboard. I mean, I have:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return self.viewModels[indexPath.item].viewSize // for programmatically created cells
}
And in viewModel class:
class ViewModel {
var viewSize:CGSize = CGSizeMake(50, 50)//for example
}
Also, I have cells from storyboard and size of it I setted in storyboard. For example, I have 5 different cells in storyboard and each has different size. Should I do ?
let viewModel = self.viewModels[indexPath.item]
if viewModel.id == "CELL_1" { return CGSizeMake(10, 10) }
else if viewModel.id == "CELL_2" { return CGSizeMake(20, 20) }
//and so on
I don't want to make this :(
Do you know solution how I can get size of item from storyboard or create behaviour when this method is not implemented? (I mean, if you don't implement this method then collection view will get size from storyboard)
You can instantiate the cell using its reuse ID and call:
cell.setNeedsLayout()
cell.layoutIfNeeded()
then access the cell's frame to get it's actual size.
Related
I have 3 collectionViews in one view controller. One has all cells of the same width and does not change. The other I would like to set the cell width in code using a constant. The last will calculate its cell's width based on the previous constant and other factors in a custom flow layout.
I tried
extension YourViewController: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == neededCollectionView {
return CGSize(width: constantWidth, height: constHeight)
} else {
// here ignore all the others and tell them to get this size as usual
}
}
}
but this seems to modify the width for all the collection views .. How do I get this two ignore the 2 collection views that have their size calculated somewhere else
in this function you can't ignore,
you need to return
CGSize
always.
try a break point and check if it gets into the else closure if so
you could try to add this line,
return CGSize(width: yourDefualtWidthConst, height:yourDefualtHeightconst)
or this
return collectionView.itemSize
You have Two options
FIRST OPTION : Return the estimatedItemSize of each CollectionViewCell
1. Get an IBOutlet Reference from each CollectionViewFlowLayout
#IBOutlet weak var firstFlowLayout: UICollectionViewFlowLayout!
2. Add UICollectionViewDelegateFlowLayout delegate in your viewController
3. Implement the delegate method
func collectionView( _ collectionView: UICollectionView,layoutcollectionViewLayout: UICollectionViewLayout,sizeForItemAtindexPath: IndexPath) -> CGSize {
if collectionView.tag == 1 {
return CGSize(width: constantWidth, height: constHeight)
} elseIf collectionView.tag == 2 {
return firstFlowLayout.estimatedItemSize
}
}
SECOND OPTION : Adepter Design pattern
You can also use adapter class for each collectionview and each adapter has a different implementation to resolve this like this
https://stackoverflow.com/a/50920915/5233180
NOT : This implementation for TableView you can make it for collectionview
I need to create a UIcollectionview with grids of cells with 3 cells per row. After a certain amount of cells, lets say 60, I need to display a big cell with image view inside of it, and the cells needs to be in full width of the UIcollectionview.
I have done created the 3 cells part, but I failed to figure out how to insert the big image cell and how to layout it properly.
Thanks.
How about you do a few sections? And the header is the other cell. You can put an image there as well.
You could specify the size of the cell in the UICollectionViewFlowLayout delegate method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//logic to find if the cell is 60 th item and then and dont forget to return the size of the other cells.
if 60th cell {
return size
} else {
return size
}
}
On an iPhone 6 Plus, the collection view cells are fine, but when testing on another device size like the iPhone 5, i'm bombarded with "
017-06-15 01:59:25.744 HyperTest[3865:8825385] The behavior of the UICollectionViewFlowLayout is not defined because:
2017-06-15 01:59:25.744 HyperTest[3865:8825385] the item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
2017-06-15 01:59:25.745 HyperTest[3865:8825385] The relevant UICollectionViewFlowLayout instance is , and it is attached to ; layer = ; contentOffset: {0, 0}; contentSize: {414, 219}> collection view layout: .
2017-06-15 01:59:25.745 HyperTest[3865:8825385] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
especially when I do this:
let layout = billFeedCollectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. It's feature of UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true
layout?.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
anyway, I can resolve this problem? I need to ensure that the cell scales and fits all devices.
By confirming UICollectionViewDelegate from your class. The delegate method sizeForItemAtIndexPath will call from UICollectionViewDelegateFlowLayout and this will call in your class now you can return the size of UICollectionViewCell.
This works for me
method:
collectionView.delegate = self
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(collectionView.frame.size.width, collectionView.frame.size.height)
}
It's also possible that when you have a collectionView in a UITableViewCell this cell actually resizes the collectionView height to 0.
In my case, the collectionView is showing an array of UIImageView to display some image gallery in each cell.
If the cell is not supposed to show any images (text without image for example), the collectionView is hidden.
The collectionView, if put within a StackView might need to have a height constraint and to avoid conflicts, you might need to set it to priority 999.
Then, when you hide the collectionView, it will actually try to set the height to 0.
The collectionView can still contain images in it and that will trigger the error.
SOLUTION: empty your datasource and reload the collectionView when you configure your cell.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let cellsize = CGSize(width: (CollectionView.bounds.size.width/2) - 12, height:(CollectionView.bounds.size.height/3) - 20)
return cellsize
}
here CollectionView is outlet of UICollectionView
I'm finding friction when trying to create responsive / adaptive UICollectionViewCells in the UIStoryboard.
The issue I'm seeing is that you don't seem to be able to set the Cell Size per Size Class and I'm trying to ascertain the right approach to this. I've designed the cells to adjust to their containers, so that they should autosize regardless of size class. This mostly works in that if I change the size class, select my cell view and do Update Frames then they all resize to fit their new size. However it's a one shot deal, if I go back to the Any/Any size class then I'm still seeing that resized version.
Here's what I'm aware I could try:
Create multiple cells, with fixed dimensions, one per size class and in the Storyboard view. I could then only use the right one at runtime but I could then see them at design time.
I could create a collection view Per Size class, each one only being installed for that size. This would work, but would be a pain to manage the multiple UICollectionViews
Create my interface and/or constraints programmatically (losing visibility of the design).
I'm hoping this is a solved scenario and I'm just missing something, but I'm aware that it could be that the IB tools don't match the code at this point.
The solution I came up with was just to implement the UICollectionViewDelegateFlowLayout and implement the sizeForItemAtIndexPath method.
This means that the cell dimensions can be set to match the available Size Class dimensions.
This still isn't ideal as you can't see the changes in the storyboard and you can't create a universal design and see it in each of the different formats.
I'm still hoping someone has a better option.
Here's a similar solution coded-out in Swift. I just styled both of my cells in the storyboard and leave them viewable for any size class combination. When the trait collection changes I update the cellSize and cellReuseID I want to use and tell the collectionView to reload all the visible cells. Then
collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
and
override func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
(not shown in sample code) are called which lets me update the size of the cell and update the cell's storyboard styling. So not entirely done in storyboard, but good enough until more support is provided in Xcode.
struct MyCollectionViewConstants{
static let CELL_ANY_ANY_REUSE_ID = "cell";
static let CELL_COMPACT_REGULAR_REUSE_ID = "cellSmall"
static let CELL_ANY_ANY_SIZE = 100;
static let CELL_COMPACT_REGULAR_SIZE = 70;
}
class MyCollectionView: UICollectionViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var cellSize = MyCollectionViewConstants.CELL_ANY_ANY_SIZE
var cellReuseID = MyCollectionViewConstants.CELL_ANY_ANY_REUSE_ID
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
return CGSize(width: cellSize, height: cellSize)
}
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact
&& self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClass.Regular){
cellSize = MyCollectionViewConstants.CELL_COMPACT_REGULAR_SIZE
cellReuseID = MyCollectionViewConstants.CELL_COMPACT_REGULAR_REUSE_ID
} else {
cellSize = MyCollectionViewConstants.CELL_ANY_ANY_SIZE
cellReuseID = MyCollectionViewConstants.CELL_ANY_ANY_REUSE_ID
}
self.collectionView.reloadItemsAtIndexPaths(
self.collectionView.indexPathsForVisibleItems())
}
}
I was stuck with same problem after implementing size class(iPad and iPhone).Well, I figured out a solution. Hope it helps!
Implement UICollectionViewDelegateFlowLayout.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
var device = UIDevice.currentDevice().model
var cellSize:CGSize = CGSizeMake(155, 109)
if (device == "iPad" || device == "iPad Simulator") {
cellSize = CGSizeMake(240, 220)
}
return cellSize
}
Swift 4
Hi Fellow Developers,
This is easy to do if the height and width of the UICollectionViewCell are same.
Steps
1. Import ** UICollectionViewDelegateFlowLayout**
2. Add the sizeForItem IndexPath method of UICOllectionView as follows
func collectionView(_ collectionView : UICollectionView,layout collectionViewLayout:UICollectionViewLayout,sizeForItemAt indexPath:IndexPath) -> CGSize {
return CGSize(width: collectionVw.frame.size.height, height: collectionVw.frame.size.height)
}
Note: What happening is you are setting the height of the UICollectionView as height and width of the UICollectionViewCell
Happy Coding :)
I'm using a UICollectionView with two prototype cells. The prototype cells have different widths and contain different controls (image view and web view). I'm definitely returning the correct prototype cell for a given index (all the cells display the correct content), but the prototype cell size is ignored and the collection view's item size is used instead. It's not like I'm manually setting the size. What's the point of allowing the prototype cell to be sized in storyboard if the property is just ignored when it's actually displayed?
The size of the cell in the storyboard editor is just to help you design the cell. Since each prototype cell can be a different size, UICollectionView doesn't know which cell's size to pick for all the cells. That's why you set the actual size you want to use for your cells separately. You can do it in the designer by selecting the collection view and setting its Cell Size Width and Height in the Size inspector, under the "Collection View Size" heading.
Or, you can override the following method and return a CGSize object that specifies the size you want to use for each cell. Using this method, you can actually have each cell be a different size:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
Example:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(100, 100);
}
Your view controller needs to be a delegate of UICollectionViewDelegateFlowLayout in order for this method to be called. So don't forget to add that delegate declaration to your view controller's .h file, such as:
#interface MyViewController () <UICollectionViewDelegateFlowLayout>
Swift
extension MyViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
If all the cells are the same size and you can set the itemSize on the UICollectionViewFlowLayout
Example:
((UICollectionViewFlowLayout *) self.collectionViewLayout).itemSize = CGSizeMake(100, 100);
Swift, working for latest iOS.
(collectionView.collectionViewLayout as! UICollectionViewFlowLayout).itemSize = cellSize
I don't have enough points to reply to Sam, but the key is that it's not UICollectionViewLayout but UICollectionViewFlowLayout.
I had a Similar Issue, I set a breakpoint on the sizeForItemAt and it was being called for every cell I had on my collection, however, the size set in code was never being reflected on the device.
While looking at other collections in my project I noticed the Collection View Flow Layout was slightly different in the collection with the issue.
By Selecting the Flow Layout in the Storyboard
And then Setting the Cell Size & Estimate Size in the Size Inspector
This helped me solve my issue.
However the size of the cell will not be dynamic with what is in your sizeForItemAt
You can set size of UICollectionViewCell in UICollectionView properties.
In Storyboard, Select Collection View at the Document Outline, then edit your Cell Size in Size Inspector.
And you should change Cell's size type to Default, if it is Custom.
In Swift you can call sizeForItemAtIndexPath delegate method to set the size for each cell.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row == 0 {
// Set the size for cell no. 0
}else if indexPath.row == 1 {
// Set the size for cell no. 1
}
}
I hope that helps. Thanks!
Try to use UICollectionViewDelegateFlowLayout method. In Xcode 11 or later, you need to set Estimate Size to none from Storyboard.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:
UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 170
let collectionViewSize = advertCollectionView.frame.size.width - padding
return CGSize(width: collectionViewSize/2, height: collectionViewSize/2)
}
If all your cells are the same size, you should simply set the UICollectionView's Cell size properties to the same as the UICollectionViewCell's size properties.
Use its Default Delegate . . .
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize mElementSize;
mElementSize = CGSizeMake(SCREEN_WIDTH , SCREEN_HEIGHT *0.60);
return mElementSize;
}