UICollectionView, Incorrect image size on first load - ios

I have a very simple demo which use UICollectionView to show a list of pictures, I use a customize cell class which contains an UIImage and a Label as below, there is no constraint.
Then I set the size of the cell in code as below. there are 3 columns on each row. all margins are set to 10.
// Calculate size of cell
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let totalSpace = marginLeft + marginRight + (colomnSpacing * CGFloat(numberOfItemsPerRow - 1))
let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(numberOfItemsPerRow))
// 20 is for label height.
return CGSize(width: size, height: size + 20)
}
// Section margin, if you only have one section, then this is the CollectionView's margin.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: marginTop, left: marginLeft, bottom: marginBottom, right: marginRight) // top, left, bottom, right.
}
// Set row spacing
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return rowSpacing
}
// Set column spacing
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return colomnSpacing
}
Here is the code used to set the image and label size. the image's width takes 1/3 width of the screen(exclude the column space), and the height equals to width. the label width was equal to image width, label's height was set to 20.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! SearchImageCell
// Set image size
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height - 20)
cell.imageView.contentMode = .ScaleAspectFill
let currentItem = self.responseArray[indexPath.row]
let imageUrl = currentItem["imgurl"] as? String
let url = NSURL(string: imageUrl!)
// Load the image
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let data = NSData(contentsOfURL: url!)
dispatch_async(dispatch_get_main_queue(), {
cell.imageView.image = UIImage(data: data!)
})
}
// Set label size
cell.imageDesc.frame = CGRect(x: 0, y: cell.imageView.frame.height, width: cell.frame.width, height: 20)
let shujia = currentItem["shujia"] as? String
cell.imageDesc.text = shujia!
cell.imageDesc.font = cell.imageDesc.font.fontWithSize(14)
cell.imageDesc.textAlignment = .Center
return cell
}
The question is:
When I load the image first, the size of the image was incorrect, like below, you can see the image's height occupy entire height of the cell, and the label was not show up.
But, when I scroll down and up(This makes the cell being reused), the image was resized, and I got the correct result, this is what I want!
I tried to add some debug code to print the size of the cell/image/label, it seems all size was correct.
cell width: 100.0
cell height: 120.0
image width: 100.0
image height: 100.0
label width: 100.0
label height: 20.0
So, what's wrong?
There are some similar question on SO(here, here), but the answers not works for me, like
1. cell.layoutIfNeeded()
2. self.collectionViewLayout.invalidateLayout()
3. self.collectionView?.layoutIfNeeded()
Please help, let me know if you need more code.

set translatesAutoresizingMaskIntoConstraints to true for your image view and label. maybe it works.
Notes: when use IB it will set them to false

Related

UICollectionView Change Cell size after start scrolling

So I have UICollectionView on my Controller
UICollection view consist of two sections.
So when I start scrolling, it changes cell sizes
My code for layout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let section = indexPath.section
let row = indexPath.row
if section == 0 {
if (row == 0){
let width = view.frame.width
return.init(width: width, height: 120)
}else{
let width = view.frame.width
return.init(width: width, height: 50)
}
}else{
//
//return.init(width: 200, height: 120)
let width = collectionView.bounds.width / 2 - 10
return.init(width: width, height: 253)
}
}
Producs are in second section, but it looks like it gives a width from first section
Make sure estimateSize is set to NONE in storyboard.
Your error the first size for section 0 but anther section you changeded the cell size you can use this code :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width / 2 - 10
let height = collectionView.bounds.height
return.init(width: width, height: height)
}

UICollectionView Horizontal align scroll first cell center alignment not exactly placing center position very first time using Swift

My scenario, I am trying to Implement UICollectionView horizontal scrollview first and last cell center alignment based on selection. Here, Very first time CollectionViewCell first cell not showing exact center position. If I didselect once everything working fine.
I need to fix very first time app open first cell not showing exact center position.
NOTE: I didn’t added header and footer also I am done my designing using storyboard.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = self.items[indexPath.row]
let cellWidth = text.size(withAttributes:[.font: UIFont.systemFont(ofSize: 14.0)]).width + 10.0
return CGSize(width: cellWidth, height: collectionView.bounds.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let inset: CGFloat = collectionView.frame.width * 0.5 - 52 * 0.5
return UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
}
You cannot use 52 directly since as the cell width, since the width of each cell is dynamic.
You need to calculate the leftInsets and rightInsets of the collectionView based on the actual width of first and last cell.
You can calculate the width of first and last cell the same way you did in collectionView(_:layout:sizeForItemAt) method using the first and last element of items array, i.e.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
//Calculate width of first cell
var firstCellWidth: CGFloat = 52.0
if let textAtFirstIndex = self.items.first {
firstCellWidth = textAtFirstIndex.size(withAttributes:[.font: UIFont.systemFont(ofSize: 14.0)]).width + 10.0
}
//Calculate width of last cell
var lastCellWidth: CGFloat = 52.0
if let textAtLastIndex = self.items.last {
lastCellWidth = textAtLastIndex.size(withAttributes:[.font: UIFont.systemFont(ofSize: 14.0)]).width + 10.0
}
//Calculate leftInset and rightInset
let leftInset: CGFloat = (collectionView.frame.width * 0.5) - (firstCellWidth * 0.5)
let rightInset: CGFloat = (collectionView.frame.width * 0.5) - (lastCellWidth * 0.5)
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
Example:
Let me know in case you still face any issues.

how to use center vertically cells in collection view in paging mode in swift 4?

I read
How to vertically center UICollectionView content
But when I used the codes here
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let navBarHeight = self.navigationController?.navigationBar.frame.height
let collectionViewHeight = (self.collectionView?.frame.height)! - navBarHeight!
let itemsHeight = self.collectionView?.contentSize.height
let topInset = ( collectionViewHeight - itemsHeight! ) / 4
return UIEdgeInsetsMake(topInset ,0, 0 , 0)
}
But when scrolling collection view this will show incorrect form
so here is my codes because my collection view cells are square and equal to screen width
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
showImagesCV.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
if let flowLayout = showImagesCV.collectionViewLayout as? UICollectionViewFlowLayout{
cellWidth = UIScreen.main.bounds.width
cellHeight = cellWidth //yourCellHeight = cellWidth if u want square cell
flowLayout.minimumLineSpacing = spacing
flowLayout.minimumInteritemSpacing = spacing
UIView.performWithoutAnimation {
}
}
}
}
extension showImagesViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: cellWidth, height: cellHeight)
}
}
I want the spacing between cells be 0 and each cell that is showing (current page (because the collection view is in paging mode)) be the center of the screen
Regarding the SO Question you mentioned and using, that is to centre the whole section not individual cells. Some of your code might be invalid(needs to be redone).
First and most important, set the constraints of your collectionView for full screen. Remember to take safeAre/TopGuide into account, this will make sure that collection view is below the navigation bar if there is one.
This will make sure that your collectionView is up to date
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myCollectionView.collectionViewLayout.invalidateLayout()
}
Dump/comment below code and set insets to (0,0,0,0) in inspector of collectionView in story board. In same inspector change Min Spacing to 0 for cells and for line, both.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let navBarHeight = self.navigationController?.navigationBar.frame.height
let collectionViewHeight = (self.collectionView?.frame.height)! - navBarHeight!
let itemsHeight = self.collectionView?.contentSize.height
let topInset = ( collectionViewHeight - itemsHeight! ) / 4
return UIEdgeInsetsMake(topInset ,0, 0 , 0)
}
Also remove/comment below code
showImagesCV.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
if let flowLayout = showImagesCV.collectionViewLayout as? UICollectionViewFlowLayout{
cellWidth = UIScreen.main.bounds.width
cellHeight = cellWidth //yourCellHeight = cellWidth if u want square cell
flowLayout.minimumLineSpacing = spacing
flowLayout.minimumInteritemSpacing = spacing
UIView.performWithoutAnimation {
}
}
Now change your sizeForItemAt like this.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.frame.size
}
UICollectionViewCell
Now in your collectionView cell, make sure that your image view is 1:1 and in centre. Also handle constraints of your other UI components.
PS : This is easy approach, other would be to write custom flowLayout

Collectionview Fixed Cell Spacing

I've been looking for how to make UICollectionView have a fixed spacing between its cells, but have not found a solution so far.
I have a UICollectionView which consists of two columns, and all the cells have the same width, but they vary in height. I have only one section in this UICollectionView.
I have set the minimum spacing to 10(which is the value I want between cells vertically), but the system is seemingly randomly stretching the vertical space.
What I would like to achieve, is the effect Wikipedia for iPad has: different article tiles with the same width, but adjustable height according to need, yet all the cells are spaces evenly vertically. (I don't mind leaving space at the very end, but I don't want extra spaces in the middle)
How should I achieve this? I would accept third-party libraries if it is easy to migrate existing UICollectionViews(and it's open-source, of course).
In swift 3,
you can implement this delegate method for cell spacing in collectionView :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
// Give cell width and height
return CGSize(width: 750, height: 320)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{
// splace between two cell horizonatally
return 50
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat{
// splace between two cell vertically
return 100
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets{
// give space top left bottom and right for cells
return UIEdgeInsets(top: 0, left: 100, bottom: 100, right: 100)
}
All right, so the reason after all is that the spacing that I've set was the MINIMUM spacing between cells. The collectionView could use whatever spacing needed to align cells on the same row, as long as it's not less than the minimum.
If you want to have fixed inter item spacing on UICollectionView you can use the custom layout below. Note: I've created this layout for fixed height cells and .vertical scroll direction.
class CollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let layoutAttributes = super.layoutAttributesForElements(in: rect) else {
return nil
}
var startX: CGFloat = 0.0
var startY: CGFloat = 0.0
let contentWidth = collectionView?.frame.width ?? 0
for layoutAttributes in layoutAttributes {
let size = layoutAttributes.size
if (startX + size.width + minimumInteritemSpacing) > contentWidth {
startX = 0
startY += size.height
startY += minimumLineSpacing
}
let origin = CGPoint(x: startX, y: startY)
let frame = CGRect(origin: origin, size: size)
layoutAttributes.frame = frame
startX += size.width + minimumInteritemSpacing
}
return layoutAttributes
}
}

Calculating collectionView cell height dynamically

Im currently attempting to calculate the height of a UICollectionViewCell dynamically but using a UILabel (the dynamic property) and the current height of the cell. The height is calculated by getting height for sizeThatFits of the label within the cell and adding that value to the original height of the cell. For some reason this always returns 0 in sizeForItemAt. I'm pretty sure the problem is the size is being calculated before the cell is created thus will always return 0. Is there anyway around this? (The UI for the cell is created within the class itself, not at celllForItemAt could that also be a problem?)
var descHeight: CGFloat = 0
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "desc", for: indexPath) as! ImageDescCell
descHeight = cell.descriptionLbl.sizeThatFits(cell.frame.size).height + cell.bounds.height
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let w = collectionView.bounds.width
return CGSize(width: w, height: descHeight)
}
NSAttributedString can tell you how big an instance is, given a bound. So assuming you know how wide the label is and you know what the text is and what its attributes are you can get the size without ever creating a label:
let width = CGFloat(200)
let attributedString = NSAttributedString(string: "Test", attributes: [NSFontAttributeName : UIFont(name: "Helvetica", size: 18)!])
let boundingRect = attributedString.boundingRect(with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
swift-3.0
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
let width = self.collectionView.frame.size.width
let Height = self.view.frame.size.height - 64 // 64 is a navigation bar height minus.
let cellSize:CGSize = CGSize(width: width, height: Height )
return cellSize
}

Resources