i have to hide some of the cells of uicollectionview based on my api response.
i am setting cell size to zero in collectionview's sizeForItemAtIndexPath method based on my json response value.
but even after setting the size to zero the cell which is at 0th index is always visible.
i can not filter out and remove the data from the array. i need that data for some operations.
my UICollectionView datasource method.
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! testCell
cell.lblTest.text = "\(indexPath.item)"
return cell
}
my flow layout delegate method.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if (indexPath.section == 0 && indexPath.item == 0) || (indexPath.section == 1 && indexPath.item == 0){
return CGSize(width: 0, height: 0)
}
return CGSize(width: 50, height: 50)
}
expected result is the cell at 0th index in 0 and 1 section is hidden but it is still displayed.
You shouldn't do this by trying to set the size to 0. If you set it to 0 underlying code reads that as trying to automatically size it for you (this is why setting it to a small value close to 0 looks like it almost works). What you want to actually do is just adjust your data source. So when it asks for the number of items return the amount without the items that you want hidden, and cellForItem should just take into account that the item isn't there.
You have to set it to a number close to 0, such as 0.1, 0.01.
Related
I'm trying to have collection view with 2 column but dynamic height.
I have used Autolayout and given required constraints to the Cell
By this way I can calculate the dynamic height but its column grids fails.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MenuListCollectionViewCell
cell.frame.size.width = collectionView.frame.width/2
cell.menuList = self.menuList[indexPath.row]
let resizing = cell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.fittingSizeLevel)
return resizing
}
This is how I want it to look
Not sure what you mean by "its column grid fails".
Anyway, you need to write a custom collection view layout. The default one (UICollectionViewFlowLayout) allows you to change height of the cells by providing the sizeForItemAt, but that won't change the behavior of the layout that will always arrange cells in rows of the same height (the height of the highest cell).
If I understood correctly, you just want the same layout of this raywenderlich tutorial.
Basically:
Create a subclass of UICollectionViewLayout implementing it's methods:
collectionViewContentSize: return width and height of the collection view content
prepare: where you can calculate the sizes of cells and
collectionView content
layoutAttributesForElements: where you
return an array of UICollectionViewLayoutAttributes in the given
rect
layoutAttributesForItem: where you return the same kind
of attributes, this time specific for an item
assign an object of this class to the collection view layout property
you can use this
This code is somehow written that you can change section inset or minimumInteritemSpacing and this calculate and resize with this parameters
you can use this code or download project from Github
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let numberofItem: CGFloat = 2
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let collectionViewWidth = self.collectionView.bounds.width
let extraSpace = (numberofItem - 1) * flowLayout.minimumInteritemSpacing
let inset = flowLayout.sectionInset.right + flowLayout.sectionInset.left
let width = Int((collectionViewWidth - extraSpace - inset) / numberofItem)
return CGSize(width: width, height: width)
}
I was able to achieve your desired outcome by doing the following.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (self.view.frame.size.width/2 - 5), height: 100)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = customCollectionView.dequeueReusableCell(withReuseIdentifier: "test", for: indexPath)
cell.layer.backgroundColor = UIColor.red.cgColor
return cell
}
Make sure you have implemented all the correct delegates
UICollectionViewDelegate
UICollectionViewDatasource
UICollectionViewDelegateFlowLayout
where you see height: make it your own desired height as you have specified in your question.
KEY: Make sure in your storyboard you have set the estimate size of the collection view to NONE -> otherwise the code will not work as expected
I came across an issue in collection view in our project -
Collectionview consist of 4 sections. Each section consist of 1 cell.
If we keep height zero for a collection view cell in section 0, cellforrrowatindexpath will not be called for section zero. The problem we are facing is cellforrowatindexapath will not be called for subsequent sections as well. Collection view will not display anything on the screen though other section cells heights are provided.
Updated:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
Switch indexpath.section {
case 0: return CGSize(width: UIScreen.main.bounds.width, height: 0)
case 1: return CGSize(width: UIScreen.main.bounds.width, height: 200)
case 2: return CGSize(width: UIScreen.main.bounds.width, height: 300)
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
Switch indexpath.section {
case 0: return A()
case 1: return B()
case 2: return C()
}
}
If anyone has better understanding of why collection view works in this way please let me know.
If you use storyboard, try to change Collection View's Estimate Size to None in Size Inspector section.
Please check:
I had a similar issue once. It might be resolved by returning CGFloat.leastNonzeroMagnitude or 0.1, instead of 0.
Add this in viewDidLoad():
if let fl = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
fl.estimatedItemSize = CGSize(width: 1, height: 1)
}
Now sizeForItemAt and cellForItemAt will be called for your additional sections.
In my UICollectionView, the last cell indexPath is 10. I want the last cell to fill the width. Now there are 2 cells each line, but I want the last cell to fill the whole width. This is what I have tried;
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (view.frame.size.width - 40) / 2
if indexPath.item == 10 {
return CGSize(width: view.frame.size.width, height: CGFloat(100))
}
return CGSize(width: width, height: 100)
}
When I run the app and press the last cell, this output run:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
}
But somehow, this doesn't do anything. Any tips please?
Got it working by myself. I forgot to add the UICollectionViewDelegateFlowLayout.
my qustion is simple.I have searched a lot and i'm familiar with UICollectionView but never saw this behavior of UICollectionView. please don't answer quickly and please read all my question.
I'm working on a project that uses collectionView (and auto layout). my collectionView is paging enabled and is working fine. but when I scroll it too fast (I mean very too fast) it scrolls two pages and shows the contents for next next row (previous previous row). also this code :
decelerationRate = UIScrollViewDecelerationRateFast
didn't work.
I created a temp project that have 1 controller and a simple collectionView (that is paging enabled and the cells's size are same as the viewController size). and there is just a label in the cell. the lable number is same as the collectionView row. also set this:
decelerationRate = UIScrollViewDecelerationRateFast
when I scroll it very fast from 1 to 2, it stops on 3 or vice versa.
this is my temp project code (that is auto layout):
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout.sectionInset = UIEdgeInsets.zero
collectionViewLayout.minimumLineSpacing = 0
collectionViewLayout.minimumInteritemSpacing = 0
collectionViewLayout.scrollDirection = UICollectionViewScrollDirection.horizontal
self.collView.collectionViewLayout = collectionViewLayout
self.collView.decelerationRate = UIScrollViewDecelerationRateFast
}
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollCell
print("cell For Item At Row \(indexPath.row)")
cell.lblNumber.text = "\(indexPath.row)"
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: collectionView.frame.size.width, height: collectionView.frame.size.height)
}
anybody knows what should I do? is it a normal behavior for UICollectionView?
How can I make a horizontal scrolling collectionView that fills up cells going across the rows rather than down the columns?
I want there to 5 columns and 3 rows but when there is more than 15 items I want it to scroll to the next page. I'm having a lot of trouble getting this going.
Where you have a reference to your UICollectionViewFlowLayout(), just do:
layout.scrollDirection = .horizontal
Here is a nice tutorial for more info: https://www.youtube.com/watch?v=Ko9oNhlTwH0
Though for historical purposes, consider searching StackOverFlow quickly to make sure this isn't a duplicate.
Hope this helps.
Update:
Your items will fill horizontally first and if there is not enough room within the collectionview going to the right, they will go to next row. So, start by increasing your collectionview.contentsize (should be larger the screen to enable scrolling) and then set your collectionview item (cell) size.
flowLayout.itemSize = CGSize(width: collectionView.contentSize.width/5, height: collectionView.contentSize.height/3)
Option 1 - Recommended
Use custom layouts for your collection view. This is the right way to do this and it gives you a lot of control over how you want your cells to fill the collection view.
Here is a UICollectionView Custom Layout Tutorial from "raywenderlich"
Option 2
This is more like a hackish way of doing what you want. In this method you can access your data source in an order to simulate the style you need. I'll explain it in the code:
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
let rows = 3
let columnsInFirstPage = 5
// calculate number of columns needed to display all items
var columns: Int { return myArray.count<=columnsInFirstPage ? myArray.count : myArray.count > rows*columnsInFirstPage ? (myArray.count-1)/rows + 1 : columnsInFirstPage }
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return columns*rows
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
//These three lines will convert the index to a new index that will simulate the collection view as if it was being filled horizontally
let i = indexPath.item / rows
let j = indexPath.item % rows
let item = j*columns+i
guard item < myArray.count else {
//If item is not in myArray range then return an empty hidden cell in order to continue the layout
cell.hidden = true
return cell
}
cell.hidden = false
//Rest of your cell setup, Now to access your data You need to use the new "item" instead of "indexPath.item"
//like: cell.myLabel.text = "\(myArray[item])"
return cell
}
Here is this code in action:
*The "Add" button just adds another number to myArray and reloads the collection view to demonstrate how it would look with different number of items in myArray
Edit - Group items into pages:
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
let rows = 3
let columnsInPage = 5
var itemsInPage: Int { return columnsInPage*rows }
var columns: Int { return myArray.count%itemsInPage <= columnsInPage ? ((myArray.count/itemsInPage)*columnsInPage) + (myArray.count%itemsInPage) : ((myArray.count/itemsInPage)+1)*columnsInPage }
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return columns*rows
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
let t = indexPath.item / itemsInPage
let i = indexPath.item / rows - t*columnsInPage
let j = indexPath.item % rows
let item = (j*columnsInPage+i) + t*itemsInPage
guard item < myArray.count else {
cell.hidden = true
return cell
}
cell.hidden = false
return cell
}
Specify the height of the collection view and cell size. More details below:
Set the constraints of the UICollectionView, pinning the edges. Be sure to specify the UICollectionView's height or constraints so it's clear the cells can only scroll horizontally and not go down to the next line. The height should be the same or slightly larger than the cell height you specify in step 2.
Implement the UICollectionViewDelegateFlowLayout delegate and sizeForItemAt method. Here's a sample sizeForItemAt implementation.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth = 100
let cellHeight = 30
return CGSize(width: cellWidth, height: cellHeight)
}