SWIFT - Collection View Cell size - ios

I have an app that uses photos from web and put them into the collection view.
in collection view i have 3 rows of cells 1:1 size which calculates from screen width / 3.
every thing is working good but there is a thing, on for ex. iphone 6s+ the cells are all tightly get together with no spacings at all. but on iphone 5s i getting some spacing between cells, in only vertical way as on the screenshot.
iPhone 6s+ Screenshot
iPhone 5s Screenshot
there is some code:
let screenSize: CGRect = UIScreen.mainScreen().bounds
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
return CGSize(width: screenSize.width / 3, height: screenSize.width / 3)
}
i have also checked if it is an image view problem but it is not.
What can i do to remove those spacings?

Creating CollectionView and then fit cells and spacing programmatically, you can try to add minimumInteritemSpacingForSectionAtIndex and minimumLineSpacingForSectionAtIndex on your own code.
func collectionViewLaunch() {
// layout of collectionView
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
// item size
layout.itemSize = CGSizeMake(self.view.frame.width / 3, self.view.frame.size.width / 3)
// direction of scrolling
layout.scrollDirection = UICollectionViewScrollDirection.Vertical
// define frame of collectionView
let frame = CGRectMake(0, 0,
self.view.frame.size.width, self.view.frame.size.height - self.tabBarController!.tabBar.frame.size.height - self.navigationController!.navigationBar.frame.size.height)
// declare collectionView
collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.alwaysBounceVertical = true
collectionView.backgroundColor = .whiteColor()
self.view.addSubview(collectionView)
self.collectionView.hidden = false
// define cell for collection view
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
// call function to load posts
loadPosts()
}
// cell line spacing
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
// cell inter spacing
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
// cell numb
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return picArray.count
}
Hope help you.

Do the original sum, round it down and multiply back by the number of columns.
Adjust the frame width of the collectView to this value and then cell widths will always fit the view perfectly.

Since the iPhone 5s' width is 640, dividing it by 3 would result in 213,33333333. Since iOS doesn't like those values, it will correct the value to 213, which will create a spacing when having three of those next to each other, since 213*3 does not equal 640. On the iPhone 6s Plus, the width is 1080 which is equally dividable by 3, resulting in 360, Here, no spacing will occur.
Try finding a divider which equals a natural number for any screen size and the spaces should be gone, or you try to find another solution.

I had a similar issue with horizontal and vertical collection views.
I was using a slider to set the number of columns and then resize the UIImages in the collection accordingly to the new cell size
On top of this I cut an image of an arrow in two and placed each half at the sides of the collection view so that when it displayed it had the result of drawing a full arrow in between images... I couldn't be bothered with custom seperator inserts at the time...which is where the arrow "should" have lived.
Hence every now and then I had a pixel wide gap in the arrows depending on screensize and orientation.
It IS all in the rounding up of the division result.
You have to find a width/height that divides as best you can to fit the screen most used.
You can apply some conditional resizing of the views if the values from the sums are not integers but finding the correct value to replace it with meant having to specify every eventuality.
In the end I gave up and "lived" with the single-pixel-wide gap

Related

UICollectionView cell ignores UICollectionViewDelegateFlowLayout - sizeForItemAt

I am trying to layout the size of my collectionViewCell so that in each row there are 7 cells (easy right?).
The cell is very simple, it only has a UILabel with top, left, bottom and right constraints of 0.
I have setup the sizeForItemAt method as below, made sure my controller is a collectionViewDelegateFlowLayout, collectionViewDelegate and collectionViewDataSource, and that collectionview.delegate and .datasource = self.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let safeScreenWidth = self.view.frame.size.width - 30
let width: CGFloat = (safeScreenWidth / 7)
let height: CGFloat = 40.0
return CGSize(width: width, height: height)
}
The method is called correctly (checked with break points and print()) however the cell decides to totally ignore the size given and comes out with a size just enough to contain its label's text (if the label's text is empty the app crashes for "Invalid parameter not satisfying: !CGSizeEqualToSize(size, CGSizeZero)").
I tried setting different sizes such width = 200, height = 200; but nothing changes the cell is always very small, as I said, just enough to fit its label's text.
A quick solve is setting height and width constraints for the label in cellForItemAt, but I want to understand why sizeForItemAt does not work.
For some reason, automatic estimated size has bigger priority, than calculated in sizeForItemAt method.
Try to set collectionViews Estimate Size to None

Collection View Cells are resizing unintentionally when scrolling to bottom of CollectionView

I am building a collection view (gallery) of images.
The layout is 3 cells per row.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width: CGFloat = (view.frame.width / 3) - 8
let height: CGFloat = width
return CGSize(width: width, height: height)
}
Everything looks great until I scroll to the bottom of the collection view.
it goes from:
3 per row, looks good
to:
super blown up picture, larger than the screen
I also get this error message:
The behavior of the UICollectionViewFlowLayout is not defined because:
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.
Please check the values returned by the delegate.
The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7fa97f708c70>, and it is attached to <UICollectionView: 0x7fa981022000; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000019986f0>; layer = <CALayer: 0x6000017a8820>; contentOffset: {0, 498.33333333333331}; contentSize: {414, 1250}; adjustedContentInset: {88, 0, 34, 0}; layout: <UICollectionViewFlowLayout: 0x7fa97f708c70>; dataSource: <MarsRover.GalleryCollectionVC: 0x7fa97f50b610>>.
any insight would be great, I want to turn this into infinite scrolling (api prefetching) too down the road, just fyi if that means I can ignore this.
The error you're getting is pointing at the item's width as the problem, it is saying, basically that the item(s) width and spacing cannot be more than the collection view's width. This is because your collection view has a vertical scrolling.
So to achieve a correct behavior for your collection view you must set your controller as the delegate for the collection view and also adopt the UICollectionViewDelegateFlowLayout. In your case I can see you've already implemented the collectionView(_:, layout:, sizeForItemAt: method.
In your implementation there is a clear intention to divide the collectionView's width in three equal parts. The calculation is considering a third part of the self.view.width minus eight. If I assume correctly you're intending to left an inter-item spacing of 8. If that's the case you should specify it into another method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 8
}
That will specify the inter-item spacing to 8 points.
Continuing with the cell's width and height, you must then divide the collectionView.frame.width but before that you must subtract the inter-spacing value from this quantity, because that is the remaining space for your cells.
So your implementation would be
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// We subtract 16 because there are 2 inter item spaces like:
// [CELL] [spacing:8] [CELL] [spacing:8] [CELL]
let usableWidth = collectionView.frame.width - (2 * 8)
let cellWidth: CGFloat = usableWidth / 3
let cellHeight: CGFloat = cellWidth
return CGSize(width: cellWidth, height: cellHeight)
}
This should do the layout for you.
Since all of your items are they same size you should not be setting them in the delegate method. Instead set a static size on your flowLayout (you can drag an outlet from the storyboard) in viewDidLayoutSubviews and use the layout insets to do it so you can't possibly get the math wrong:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let remainingSpace = collectionView.bounds.width
- flowLayout.sectionInset.left
- flowLayout.sectionInset.right
- flowLayout.minimumInteritemSpacing * (Constant.numberOfItemsAcross - 1)
let dimension = remainingSpace / Constant.numberOfItemsAcross
flowLayout.itemSize = CGSize(width: dimension, height: dimension)
}
First of all, why not use bounds instead of frame?
Second, the reason this is happening is most likely because you are adjusting the layout elsewhere while the collectionView is loading cells (scrolling will make it load cells).
Are you using UIScrollViewDelegate methods to do anything with layout?

CollectionView. Maximum distance between Item. Swift 3

I know there is only minimum spacing between items and you can achieve that via size inspector or
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
Indeed on the simulator on SE my items are shown like in the image below.
But when I run on a larger simulator my distance between cell items is increasing and aren t showing as expected.
Is there any method to set like a maximum size between them ?
Logically, there is another factor that you should keep in mind for setting the space between the items in your collection view, which is the size of the cell. If you are getting the cell as is (with its default size) -and my assumption is that's your case-, it would be statically dequeued in the collection view, i.e the size of the cell would be as is.
So, what you should do is to tell what is the desired size (width) of the cells to be displayed in the collection view, by conforming to
UICollectionViewDelegateFlowLayout and implementing collectionView(_:layout:sizeForItemAt:):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// let's assume that each row in the collection view should contains 3 cells:
let desiredWidth = collectionView.frame.width / 3
// height is also equals to the width to be a square...
return CGSize(width: desiredWidth, height: desiredWidth)
}
At this point if you are setting the minimum space between items to zero, each row should contains three cells with no space between them.
If you are aiming to set a little space between the cells, you should reflect it when declaring the desired width for the cell, for example:
// since each row contains 3 cells, subtracting 2 from each cell width
// means it would be 6 points (3 * 2) divided at the whole row (2 spaces, 3 points for each)
let desiredWidth = collectionView.frame.width / 3 - 2

Swift auto layout in UICollectionView with intrinsic height of cells

I'm having a hard time with auto layout and UICollectionView. I have a vertical UICollectionVIewFlowLayout that contains two different types of cells, as in the image below:
The SingleCard cell contains a subview with an image (UIImageView). The HorizList cell contains a subview which is a horizontal UICollectionView (a list of thumbnail photos). The heights of the two cell types are as follows:
SingleCard: the width of the cell shall be the same as the screen width. The height shall be dynamic to the height of the UIImageView (to keep proportions of the image and have it fill screen width no matter of device size)
HorizList: the height is constant, say 200 points.
My attempt, in the CollectionViewController is to do
override func viewDidLoad() {
...
let layout = collectionView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.estimatedItemSize = CGSize(width: 200.0, height: 235.0)
}
This I do to get the two cell's intrinsic size to apply. Then I do not implement
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
at all.
My question is, what do I implement in
override var intrinsicContentSize: CGSize {
??
}
for the two different cell types? Maybe this is not the only thing I need to do, if so I'm grateful for additional hints.
Thanks!
/ola

Autoresizing of cells in UICollectionViewCell in accordance with the frame size

How can I make the cells in collection view auto resize so that minimum of four cells can at least fit in a single row, and if the frame size is that of iPad, then more cells could fit in row. Four cells should be the minimum number of cells in a row, please see the pictures below for further clarification:
I have a collection view which allows me to add images by using the picker view controller, at first the collection view looks like this:
I can further add images to this collection view and after adding several images, it would like:
Right now if there are four images, the fourth one goes to the next row, I want system to autoresize the cells based on the frame size so that minimum four cells are shown in one row. I'm a newbie and quite new to collection view, can someone please help me on this?
you can decide size of collectionview's cell using following method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
// Return different cells size
let screenBounds = UIScreen.main.bounds
var width = screenBounds.width
let totalLeftRightInsect = 16
let minimumsSpaceBetweenCell = 5
switch (Condition for detecting device ) {
case "iPhone":
let numberOfCell = 4
let totalSpace = CGFloat(( numberOfCell * spaceBetweenCell) + totalLeftRightInsect)
width = (width - totalSpace) / CGFloat(numberOfCell)
return CGSize(width: width, height: width)
case "ipad":
let numberOfCell = 6
let totalSpace = CGFloat(( numberOfCell * spaceBetweenCell) + totalLeftRightInsect)
width = (width - totalSpace) / CGFloat(numberOfCell)
return CGSize(width: width, height: width)
default:
return CGSize(width: 0.0, height: 0.0)
}}
Answer for Swift3, Xcode 8 with horizantal spacing fixed:
The issue with all earlier answer is that cell size is given CGSizeMake(cellWidth, cellWidth) actually takes all screen leaving no room for margin lines due to which collectionView tries to adjust the row/column spacing by taking one element less in each row and unwanted extra spacing.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let linespacing = 5 //spacing you want horizantally and vertically
let numberOfCell: CGFloat = 3 //you need to give a type as CGFloat
let cellWidth = UIScreen.mainScreen().bounds.size.width / numberOfCell
return CGSizeMake(cellWidth - linespacing, cellWidth - linespacing)
}

Resources