Custom cell width for one collectionView - ios

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

Related

UIcollectionview Update cell height

Currently I am working on one dynamic layout of collection view.
On certain clicks inside collectionview cell (ex cell has UIbutton) the cell of that click button should increase height.
i have implemented the method of collectionviewflowlayoutdelegate
SizeforItemAt:
When cell button click i am just reloading particular one indexpath
self.collectionview.reloadItems([indexpath])
It will call SizeforItemAt: but it is calling for all cell again. So due to this my collectionview jerking all cells as it’s sizes are updating.
So is there any way to update height for single cell only?
You can do like this
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
let width = collectionView.bounds.width
let height = 0.0
// for specific cell height
if indexPath.row == 1{
height = 200.0 // chnage height for a specific cell
}else{
height = collectionView.bounds.height //actual height
}
return CGSize(width: width, height: width)
}
Update
I am not sure will it work or not when clicking on the button.
But you can change the height of a specific cell that is selected by implementing didSelectItemAt like the below.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.performBatchUpdates(nil, completion: nil)
// to reload specific cell use below line.
//collectionView.reloadItemsAtIndexPaths([indexPath])
}
You can Check if the idexPath in sizeForItemAt is selected or not. if it is selected then you can change the height otherwise return the original height. like the following.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch collectionView.indexPathsForSelectedItems?.first {
case .some(indexPath):
let height = 200.0 // here you can change the height for selected cell.
return CGSize(width: view.frame.width,height: height)
default:
let height = 200.0 // standard height
return CGSize(width: view.frame.width, height: height)
}
}
Check for collection view cell dynamic height.
And do not hard code height for cell, instead make the components inside the cell define the height of the cell. In this way when tap on button pragmatically increase/decrease the size of some component inside that cell and you would get the desired result.
So is there any way to update height for single cell only?
No. If sizeForItemAt: is implemented, it can and will be called for any cells the runtime wishes. It isn't up to you what cell this is. Your job is to return the correct value for whatever the cell in question is. If you don't want to change a cell's height, don't change it, but you can't stop the call from arriving.
However, I doubt that this has anything to do with your issue. It sounds like what you want is to update the height of this one cell is a smooth (animated) manner. That has nothing to do with what you asked.

Dynamic UICollectionView in UIViewController

I am an android developer and developing my first iOS app, I need to implement GridView in order to add some social icon so after some search I found UICollectionView but it's not working as expected. How to set dynamic height of the UICollection view? Actually I want to display all icons but it showing only a row and others after scrolling the UICollectionView.
below is the example:
This is what I am getting
This is what I want:
I am using below code:
import UIKit
class CollectionViewDemo: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var socialHandleCollection: UICollectionView!
#IBOutlet weak var socialImageView: UIImageView!
var socialHandleArray:[String] = ["Facebook", "Twitter", "Youtube","Vimeo", "Instagram", "Custom URL", "Linkedin", "pinterest"]
override func viewDidLoad() {
self.socialHandleCollection.delegate = self
self.socialHandleCollection.dataSource = self
// socialHandleCollection.frame.size.height = 130
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return socialHandleArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: colvwCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! colvwCell
cell.imgCell.image = UIImage(named: "demo_img.png")
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print(self.socialHandleArray[indexPath.row])
}
}
Any help would be appreciated.
Assuming you are using autoLayout
Initially declare a specific number of columns that you desire
let numberOfColumns = 5
Okay so first things first. You will have to make your class conform to the UICollectionViewDelegateFlowLayout. Now implement the function -
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: collectionView.bounds.size.width/numberOfColumns, height: collectionView.bounds.size.width/numberOfColumns);
}
Here 5 can be changed to the number of columns you want in each row and passing same value as height will ensure that the shape is always square. And hence you can apply corner radius to make it circular.
Now moving on. From your interface builder, ctrl + click and drag the UICollectionView height constraint to your UIViewController (similar to how you would do for a UIView but do it for the constraint)
Now once you know the number of items that you need to display inside your UICollectionView, you can do something like:
//replace 5 with the number of columns you want
//array contains the items you wish to display
func figureOutHeight(){
if(array.count == 0){
//as no items to display in collection view
return
}
//ceil function is just to round off the value to the next one, for example 6/5 will return 1 but we need 2 in this case. Ensure all arguments inside ceil function are float
let rowsCount = ceil(Float(array.count)/Float(numberOfColumns))
//now you have number of rows soo just update your height constraint by multiplying row count with height of each item inside UICollectionView
collectionViewHeightConstraint.constant = rowsCount * collectionView.bounds.size.height / numberOfColumns;
view.layoutIfNeeded()
}
Also if you haven't change the scroll direction to vertical.

Mixing cells from storyboard and programmatically created cells

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.

Disable supplementary view

What is the correct of of disabling the supplementary views (e.g. header)? One time I want to show them (iPhone) and one time I don't want to show them (iPad).
The only idea I currently have is to return a size of zero in referenceSizeForHeaderInSection. But I think it's kind of an overhead to create views, which aren't used at all. On the other side I have to implement collectionView:viewForSupplementaryElementOfKind:atIndexPath: and I can't return nil, because the app crashes then.
How can I disable the supplementary views in UICollectionView?
If you return CGSizeZero from collectionView:layout:referenceSizeForHeaderInSection: delegate method collectionView:viewForSupplementaryElementOfKind:atIndexPath: won't be called.
Apple Developer API Reference states about collectionView(_:layout:referenceSizeForHeaderInSection:):
If the size in the appropriate scrolling dimension is 0, no header is added.
The following Swift 5 code snippet shows a possible implementation of collectionView(_:layout:referenceSizeForHeaderInSection:) in order to display or not UICollectionView supplementary views:
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
return CGSize.zero
} else {
return CGSize(width: 0, height: 40)
// or
//let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
//return flowLayout.headerReferenceSize
}
}

How to configure a UICollectionView Cell Size per Size Class?

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 :)

Resources