Am writing this question just as a reference and to help anyone facing the same issue I faced. Many answers here at SO and in google search didn't provide a complete solution. So Here I answer my own question
Here is the code that works and sets both width and height correctly
import UIKit
class MyViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
#IBOutlet weak var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
myCollectionView.delegate = self
myCollectionView.reloadData()
}
//Other methods here
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) //Put your Cell Identifier istead of Cell
let height = cell.contentView.frame.height
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
return .zero
}
}
Related
I am new to Swift and I am trying to create a UICollectionViewCell. It builds successfully but the cells are not the size that I thought I was creating. There are supposed to be two columns and and it is supposed to contain an image, description and price. I know it is something simple but I cannot figure out what I am doing wrong. See the image below. I assume I need to fix something in the size inspector but I cannot figure out what.
import UIKit
class ProductsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
#IBOutlet weak var productsCollection: UICollectionView!
private(set) public var products = [Product]()
override func viewDidLoad() {
super.viewDidLoad()
productsCollection.dataSource = self
productsCollection.delegate = self
}
func initProducts(category: Category){
products = DataService.instance.getProducts(forCategoryTitle: category.title)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCell", for: indexPath) as? ProductCell {
let product = products[indexPath.row]
cell.updateViews(product: product)
return cell
}
return ProductCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height: CGFloat = 280
return CGSize(width: (collectionView.frame.size.width - 10) / 2, height: height)
//10 is minimum line spacing between two columns
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
FYI
Try this:
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// You can change collection view cell height here, It's up to your design
let height: CGFloat = 150
let width: CGFloat = (collectionView.frame.size.width - 10 - a) / 2 // a is extra spacing of collection view (like leading or trailing constraints)
// Converting CGFloat to Int make sure the width of total cells are less than or equal the screen width
return CGSize(width: Int(width), height: Int(height)) // 10 is minimum line spacing between two columns
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
Please check extra spacings example with the below photo:
a = 20 with my example design (leading = 10, trailing = 10)
just provide the itemSize when you declare the collectionView:
let layout = UICollectionViewFlowLayout()
layout.itemSize = sizeForEachItem
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
This question already has answers here:
UICollectionViewLayout Not working with UIImage in Swift 5 and Xcode 11
(2 answers)
Closed 2 years ago.
I found something strange.
I created a custom UICollectionViewCell and a label inside it.
I applied auto-layout only to the top, leading, and bottom of the label.
class TestCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}
And if the text is long, it is clipped off the screen like below.
I want the text to be displayed as just "..." if it is too long.
So I applied traling auto-layout like this.
Then, collectionViewCell width is broke.
This is my code.
I already set delegate, datasource.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? TestCollectionViewCell else { return UICollectionViewCell() }
cell.label.text = "asdfasdkfjabsdkljfbasdfhaoweihf;oaisefowiejfao;wihfaksdjfhakdjf;lskdjfa;zxknb"
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionView.frame.width - 1) / 2, height: 53)
}
}
Open Storyboard and select your CollectionView.
Now open Size inspector and change Estimated size Automatic to None.
Hope this will work.
Try calculating size of cell as:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (UIScreen.main.bounds.width - 1) / 2, height: 53)
}
You can set the size of the cell using UICollectionViewFlowLayout()
In your viewDidLoad
override func viewDidLoad() {
collectionView.dataSource = self
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: (collectionView.frame.width - 1) / 2, height: 53)
collectionView.setCollectionViewLayout(layout, animated: true)
}
may I know is there any way to implement 2 UIcollectionview in 1 UIviewcontroller with 2 different UIcollectionflow layout.
The main issue is one UIcollectionview is conform to iOS UIcollectionviewflowlayout and the other is conform to a Waterfalllayout. Because the problem I faced now is I cant have both delegate functions in one UIviewcontroller. Thanks all.
func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
}
func collectionView(_ collectionView: UICollectionView, layout: WaterfallLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
}
//: Playground - noun: a place where people can play
import UIKit
class Controller: UIViewController, UICollectionViewDelegateFlowLayout, WaterfallLayoutDelegate {
let collectionView = UICollectionView(frame: CGRect.zero)
let waterfallCollectionView = UICollectionView(frame: CGRect.zero)
override func viewDidLoad() {
collectionView.delegate = self
collectionView.collectionViewLayout = UICollectionViewFlowLayout()
waterfallCollectionView.delegate = self
let waterfallLayout = WaterfallLayout()
waterfallLayout.delegate = self
waterfallCollectionView.collectionViewLayout = waterfallLayout
}
//MARK: - WaterfallLayoutDelegate
func collectionView(_ collectionView: UICollectionView, layout: WaterfallLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.zero
}
//MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.zero
}
}
//MARK: - Just for test
protocol WaterfallLayoutDelegate: class {
func collectionView(_ collectionView: UICollectionView, layout: WaterfallLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
}
class WaterfallLayout: UICollectionViewLayout {
weak var delegate: WaterfallLayoutDelegate?
}
Everything just works.
I have a UIView(TableHeaderView) which has few labels and collection view. One of the label text is set on run time. So its height is unknown. I'm setting this view as the header for table view.
let tableHeaderView = CommentsTableHeaderView.instanceFromNib() as! CommentsTableHeaderView
commentsTable.tableHeaderView = tableHeaderView
In CommentsTableHeaderview class I have the following code.
I have set postDesc label's number of lines to 0. If the text comes in few lines, its cutting off. I want this to be dynamic and display all text.
class CommentsTableHeaderView: UIView,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
#IBOutlet weak var postDesc: UILabel!
#IBOutlet weak var profileName: UILabel!
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var imageCollectionView: UICollectionView!
#IBOutlet weak var numberOfComments: UILabel!
class func instanceFromNib() -> UIView {
return UINib(nibName: "CommentsTableHeaderView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
override func awakeFromNib() {
imageCollectionView.delegate = self
imageCollectionView.dataSource = self
imageCollectionView.register(UINib(nibName: "InnerCollectionCell", bundle: nil), forCellWithReuseIdentifier: "InnerCollectionCell")
postDesc.lineBreakMode = .byWordWrapping
postDesc.numberOfLines = 0
postDesc.text = "hi"
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("inside collection view")
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("inside cell")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InnerCollectionCell", for: indexPath) as! InnerCollectionCell
cell.cellImageView.image = UIImage(named: "Recipe.jpeg")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
print("inside size\(collectionView.frame.width)")
return CGSize(width: imageCollectionView.frame.width, height: 200)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets{
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
}
}
You need a way to resize your UILabel according to the text size. Do not put height constraints on UILabel. Rather pin its bottom to the bottom view's top.
Do that programmatic resizing after viewDidLoad() - maybe in viewWillAppear() or viewDidAppear(), before you do your first reloadData() call.
Here is a nice tutorial how this should be done - it suggests doing the resizing inside viewDidLayoutSubviews(), which should also be fine.
you need 2 collectionViewDelegate methods:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: x, height: x)
}
if you wish to have a dynamic height in referenceSizeForHeaderInSection height field, set your height according to screen size for ex, UIScreen.main.bounds.height * 0.09
I got two questions.
I am wondering why my collection view automatically load data without calling imageCollectionView.reloadData().
Solved. See comment
Why func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize is not called? I didn't see the print("collectionViewLayout called") I am trying to modify the cell's size so the cell's height equals to the collection view height
Solved. See comment
Here is the codes
class ProductInternalDetailVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var selectedProduct: Product?
#IBOutlet weak var imageCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
imageCollectionView.delegate = self
imageCollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath) as! ProductInternalDetailCVC
if indexPath.row == 0 {
cell.productImage.image = selectedProduct!.image1
} else {
cell.productImage.image = selectedProduct!.image2
}
cell.productImage.frame = CGRect(x: 1, y: 1, width: cell.frame.size.width-2, height: cell.frame.size.height-2)
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
//return CGSize(width: collectionView.frame.size.height-1, height: collectionView.frame.size.height-1)
print("collectionViewLayout called")
return CGSize(width: 10, height: 10)
}
}
class ProductInternalDetailCVC: UICollectionViewCell {
#IBOutlet weak var productImage: UIImageView!
}
Thanks for the help.
Question 2: The function signature has changed a bit. Change the function signature with this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
...
}
And don't forget to implement the UICollectionViewDelegateFlowLayout protocol.
Question 1:
When entering the ViewController your collectionView loads the datasource of the collectionView automatically if this is your question.for example, make changes on datasource without changing the view (means without calling viewDidLoad method) you will not see the changes until you call imageCollectionView.reloadData().