Swift 3 - How to make UICollectionViewCell same width as label subview - swift

How can I make my UICollectionViewCell adjust its width with the label contained within the cell?
I have tried about a dozen methods to have the cell adjust its width to the label, but none have worked.
I seriously tried so many different methods to even keep track of it. Does anyone have an example on how to size UICollectionViewCells with the width of a label contained within it?
I have set UICollectionViewDelegateFlowLayout as a delegate, and added
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! TagsCollectionViewCell
//Adding text to the label inside of the cell here
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! TagsCollectionViewCell
//This is called before the creation of the cell
return CGSize(width: cell.tagLabel.intrinsicContentSize.width, height: cell.tagLabel.intrinsicContentSize.height)
}

Related

CollectionView indexPath.row is not correct? (Inside TableViewCell)

I have a collection view that I want the indexPath.row to. However, whenever I try to print/display the indexPath.row number, it's off. It's a horizontal collection view, so when I try to page right, it goes from: 0, 2, 3, 4, 5 and going backwards it jumps to 5,3,2,1,1,1. It's also varies each time.
Currently the collectionView is inside a tableViewCell. The data is getting loaded off of Firebase database.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FeedCollectionViewCell
pageController.currentPage = indexPath.row
pageController.numberOfPages = userList.count
return cell
}
Setting Prefetching enabled to false in storyboard worked.

custom cell and custom height for each

I output 4 custom cells via the switch function now I need to output either a dynamic height value or a fixed height value for each of them. I like both, as their height is always static.
I subscribed to The uiСollectionviewВelegateFlowLayout Protocol and found how to change the height of all cells
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 357, height: 600)
return size
to override the height of individual cells I use the function
cell.frame = CGRect(cell.frame.origin.x, cell.frame.origin.y, width: 100.0, height: 100.0)
but it doesn't work, "Expression type '#lvalue CGRect' is ambiguous without more context"
what should I do in this case? what function to use?
full code below
// main protocols UICollectionView
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// width and height for cell in collectionView
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 357, height: 600)
return size
}
//margins for cell in collectionView
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 40.0
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell: UICollectionViewCell!
switch indexPath.row {
case 1:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath)
as? secondCollectionViewCell
cell.frame = CGRect(cell.frame.origin.x, cell.frame.origin.y, width: 100.0, height: 100.0)
case 2:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "thirdCell", for: indexPath)
as? cellThirdCollectionViewCell
case 3:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "fourthCell", for: indexPath)
as? fourthCollectionViewCell
default:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath)
as? imageModelCollectionViewCell
}
return cell
}
}
extension ViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch indexPath.row {
case 1:
return CGSize(width: 100, height: 200)
case 2:
return CGSize(width: 100, height: 300)
case 3:
return CGSize(width: 100, height: 400)
default:
return CGSize(width: 100, height: 100)
}
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell: UICollectionViewCell!
switch indexPath.row {
case 1:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath)
as? firstCollectionViewCell
case 2:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath)
as? secondCollectionViewCell
case 3:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "thirdCell", for: indexPath)
as? thirdCollectionViewCell
default:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath)
as? imageModelCollectionViewCell
}
return cell
}
}
completely solves the problem
don't forget add
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
}
p.s. If You get an error with "collectionView.delegate = self" is a possible reason that you are working in a viewController and not in a collectionViewController
You need to add IBOutlet
Have you tried changing your function func collectionView(_:layout:sizeForItemAt:) to something like this?
let cellHeights = [ 90, 400, 100, 70 ]
// width and height for cell in collectionView
func collectionView( _ view: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath ) -> CGSize
{
return CGSize(
width: view.bounds.size.width,
height: cellHeights[indexPath.row]
)
}
Also, casting the result of collectionView.dequeueReusableCell(withReuseIdentifier:for:) to a UICollectionViewCell subclass has no effect in your code...
I might change it to something like:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
// map row numbers to cell reuse identifiers...
let ids = [
0: "secondCell",
1: "thirdCell",
2: "fourthCell"
]
// look up the reuse identifier for the row we want; if none found (subscript call returns `nil`), use `"imageCell"`
let reuseID = ids[indexPath.row] ?? "imageCell"
let cell = collectionView.dequeueReusableCell( withReuseIdentifier: reuseID, for: indexPath)
return cell
}
you can use array of height and retrieve cell height based on indexPath.
let heightArray = [90,400,100,70]
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 357, height: heightArray[indexPath.row])
}

UICollectionView Cell not changing upon selecting

In my project, I have a UICollectionView. In the UICollectionView, I have a custom cell.
I am able to print the cell value when it is selected within "didSelectItemAt", however, if I try to edit the cell in any way within this method, it does not change.
I'm sure I'm missing something, any help would be appreciated!
#IBOutlet weak var collectionView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return statValues.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCollectionViewCell", for: indexPath) as! customCollectionViewCell
cell.statLabel.text = statHeaders[indexPath.row]
cell.statLabel.textColor = UIColor(red:0.31, green:0.31, blue:0.31, alpha:1.0)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCollectionViewCell", for: indexPath) as! customCollectionViewCell
print(cell.statLabel.text)
cell.backgroundColor = UIColor.yellow
collectionView.reloadData()
}
When user selects a cell, the code is correctly printing the value of the indexPath, however the backgroundColor does not change.
My guess would be that you are creating a new instance of cell instead of using the one in the collectionView
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Change this line
let cell = collectionView.cellForItemAtIndexPath(indexPath: indexPath)
print(cell.statLabel.text)
cell.backgroundColor = UIColor.yellow
collectionView.reloadData()
}
Also, you should probably keep an external data model for your source of truth. If you have enough collectionViews that requires scrolling, when you scroll offscreen, your cells will be reused in a random order causing cells that you did not click to be yellow.
Create a seperate array such as
var selectedStatHeaders: Set<Int>()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCollectionViewCell", for: indexPath) as! customCollectionViewCell
cell.statLabel.text = statHeaders[indexPath.row]
cell.statLabel.textColor = UIColor(red:0.31, green:0.31, blue:0.31, alpha:1.0)
// Reset/configure cell each reload
if selectedStatHeaders.contains(indexPath.row) { // Can also make this into a ternary
cell.backgroundColor = UIColor.yellow
} else {
cell.backgroundColor = UIColor.whit
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedStatHeaders.insert(indexPath.row)
collectionView.reloadItemsAtIndexPath(indexPath: indexpath)
}
Hmm... Doesn't make sense if the code is able to print but the background doesn't change color. Do you mean changing back from yellow to white? Anyway, just a hunch but I suspect it's because you are calling collectionView.reloadData() after your set the backgroundColor change.
https://developer.apple.com/documentation/uikit/uicollectionview/1618078-reloaddata
This causes the collection view to discard any currently visible
items (including placeholders) and recreate items based on the current
state of the data source object.

How can we reload header view inside our collection view

I've been looking for a way to reload my collection view header. So I have a collection view header & a CollectionViewCell that only contains an image. Now when the cell is press, I would like to display the image in the header view without calling collectionView.reloadData(). This is how my didSelectItemAt & didDeselectItemAt method looks like.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedImage = images[indexPath.item]
let imageCell = collectionView.cellForItem(at: indexPath) as! CollectionCell
imageCell.photoBackgroundView.backgroundColor = .red
collectionView.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let imageCell = collectionView.cellForItem(at: indexPath) as! ImagePickerCell
imageCell.photoBackgroundView.backgroundColor = .black
}
So when I select a cell the view turns red, when I deselect it the view turns black. This video here, shows how the behavior without reloading the collectionView. Now here is were I would like to reload the header view.
If I do use collectionView.reloadData(), this is the outcome. How would I be able to reload the header or the collectionView where the header view displays the selected cell image & turns red.
You can try like global instance for that. Like
class YourClass: UIViewController {
/// Profile imageView
var profileImageview = UIImageView()
}
In CollectionView cellforItem assign a imageview. Like
let imageCell = collectionView.cellForItem(at: indexPath) as! CollectionCell
profileImageview = imageCell.imageView
Then when every you selecting collectionViewCell
You can call a function to change a image of imageView. Like
func updateImage() {
profileImageview.image = UIImage()
}
Well I'm trying to understand why sometimes you cast the cell in CollectionCell and sometimes in ImagePickerCell, anyway try to change your functions in these
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CollectionCell
cell?.photoBackgroundView.backgroundColor = .red
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CollectionCell
cell?.photoBackgroundView.backgroundColor = .black
}

UICollectionView Dynamic Cell Height as per the content | Pinterest Layouts

I am trying to do a layout as Pinterest uses for their image gallery. But the issue is that I'm returning the image height to the size of the cells and when the images have different heights, it leaves spaces in the cells.
I have walked through lots of articles, but I have not found a simple solution. Is there any simple way we can do this?
Please see this image
class ViewController: UIViewController {
#IBOutlet var collectionVIew: UICollectionView!
var imgData = [#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8")]
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width
let img = imgData[indexPath.item]
// 335 is the width of my cell image
return CGSize(width: width/2-15, height: (img.size.height / 335) * width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imgView.image = imgData[indexPath.row];
cell.imgView.contentMode = .scaleAspectFit
return cell
}
}
What you have mentioned can be achieved using flow layout — a layout class provided by UIKit.
This is what you are looking for:
https://www.raywenderlich.com/392-uicollectionview-custom-layout-tutorial-pinterest
I hope you can put some effort into reading this very simple post and follow each step carefully.
You are trying to fit non-square images into a square hole. When we do this something has to be given. In this case, I would recommend you make each of your cells a fixed height and set an aspect fill content mode on them so they fill the cell's frame completely. The downside is that some of the larger images will have part of them cut off. The other option is to use the aspect fit content mode which will scale the image to fit without losing a part of the image. This would leave you with the cell spacing again.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width
return CGSize(width: width/2-15, height: 100) // fixed height
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imgView.image = imgData[indexPath.row];
cell.imgView.contentMode = .scaleAspectFill // Set the scale mode to aspect fill
return cell
}
Alternatively, you can check out some open source libraries that might implement this for you. This one looks promising https://github.com/abdullahselek/ASCollectionView
You can check out a larger list of collection view open-source libraries here https://github.com/vsouza/awesome-ios#collection-view
If you want the cells to have different heights you need to calculate the correct height based on the aspect ratio of the image and the width that is fixed. You calculate the aspect ratio in this case by the height/width when our width is fixed and our height is dynamic. Then multiply that by the width of the cell of the image to get our dynamic cell height.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width / 2 - 15;
let img = imgData[indexPath.item]
return CGSize(width: width, height: (img.size.height / img.size.width) * width)
}
You should set your content mode to aspect fit in this case.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imgView.image = imgData[indexPath.row];
cell.imgView.contentMode = .scaleAspectFit // Set the scale mode to aspect fit
return cell
}