I am using a collectionview and the first data load is working fine. The problem occurs when the data is load a second time the labels overlaps because the old labels appear to still exist.
Below is my collectionview code, thanks in advance.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCellIdentifier", for: indexPath)
customCell.layer.borderColor = UIColor.black.cgColor
customCell.layer.borderWidth = 1
// changed these lines here
if cellsLabels[indexPath.row] != nil {
customCell.willRemoveSubview(cellsLabels[indexPath.row]!)
}
//to these lines here and the problem was solved
let maybe = customCell.subviews
for i in 0 ..< maybe.count {
maybe[i].removeFromSuperview()
}
let c
ommentLabel = UILabel()
commentLabel.text = commentsArray[indexPath.row]
commentLabel.frame = CGRect(x: 0, y: 50, width: 200, height: 30)
customCell.addSubview(commentLabel)
self.cellsLabels[indexPath.row] = commentLabel
if indexPath.row == commentLoadTracker*10 - 1 {
print("working doomfist")
}
return customCell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return commentsArray.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var cellSize = CGSize()
cellSize.width = self.commentView.frame.width
cellSize.height = 100
return cellSize
}
customCell.willRemoveSubview
You are calling willRemoveSubiew instead of removeFromSuperview
if cellsLabels[indexPath.row] != nil {
cellsLabels[indexPath.row]!.removeFromSuperview()
}
There is no need to call willRemoveSubview, UIKit calls it for you, it only exists so that it can be
Overridden by subclasses to perform additional actions before subviews are removed from the view.
Related
How to get the selected cells count and selected cells array from UICollectionView, I need to set the limit of selection. Below is my code which is not working. Please guide. Thanks
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
let indexPaths = collectionView.indexPathsForSelectedItems!
print(indexPaths)
if let selectedItems = collectionView.indexPathsForSelectedItems {
if selectedItems.count >= 3 {
collectionView.deselectItem(at: indexPath as IndexPath, animated: true)
return false
}
}
return true
}
Above code when written in didSelect method returns only selected cell index but here just skip to last line
return true
You can use the shouldSelectItemAt delegate method from the UICollectionViewDelegate to achieve that:
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return collectionView.indexPathsForSelectedItems?.count ?? 0 <= selectionLimit
}
A sample demo to demonstrate how it works:
class ViewController : UIViewController {
private var myCollectionView:UICollectionView?
private var selectionLimit = 4
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView()
view.backgroundColor = .white
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: UIScreen.main.bounds.width * 0.7, height: 60)
myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
myCollectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
myCollectionView?.backgroundColor = UIColor.white
myCollectionView?.allowsMultipleSelection = true
myCollectionView?.dataSource = self
myCollectionView?.delegate = self
view.addSubview(myCollectionView ?? UICollectionView())
self.view = view
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath)
myCell.backgroundColor = UIColor.blue
return myCell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Did select a cell at \(indexPath.row)")
let itemsLeftToSelect = selectionLimit - (collectionView.indexPathsForSelectedItems?.count ?? 0)
print("Have \(itemsLeftToSelect) items to select")
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
print("Did deselect a cell at \(indexPath.row)")
let itemsLeftToSelect = selectionLimit - (collectionView.indexPathsForSelectedItems?.count ?? 0)
print("Have \(itemsLeftToSelect) items to select")
}
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return collectionView.indexPathsForSelectedItems?.count ?? 0 < selectionLimit
}
}
When you reach the selection limit, shouldSelectItemAt will return false and didSelectItemAt function will not be called anymore. On the other hand if you click on the already selected cell, didDeselectItemAt will be called and you will be called, and you are able to select + 1 cell again, as the number of selected items will decrease with 1. See also the debug logs in the example.
A better approach is model based.
In your data model add a property
var isSelected = false
and re-/set it accordingly when a cell is de-/selected.
Then you can simply count the selected data source items – dataSource represents the data source array
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return dataSource.filter{$0.isSelected}.count < 3
}
Further there is no need to deselect a row in this method as you are preventing the user from selecting more than 2 rows.
Here the output of the code but I want to make it into 2 column
I want to make a collection view with more than 1 column but this code just shows one column. Can anyone help me how to fixed it??? Thank You....
import UIKit
class SearchViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
Here the array
var collectionArray : [String] = ["1", "2", "3", "4"]
let labelnamee = [("Long Sleeve Abstract Print Top"), ("Logo Graphic Print Top"), ("Material Study T-Shirt"), ("Mission Statement Print T-Shirt"), ("Printed Logo T-Shirt"), ("Tie-Dye Print T-Shirt")]
let labelpricee = [("RM 1021"), ("RM 860"), ("RM 750"), ("RM 750") , ("RM 670"), ("RM 760")]
let imagee = [UIImage(named: "abstractprint"),
UIImage(named: "longsleeve"),UIImage(named: "studymaterial"),UIImage(named: "mission"), UIImage(named: "white"), UIImage(named: "tiedye")]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return labelnamee.count
}
for the collection cell border
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let cellIndex = indexPath.item
cell.imageView.image = imagee[cellIndex]
cell.labelname.text = labelnamee[cellIndex]
cell.labelprice.text = labelpricee[cellIndex]
cell.contentView.layer.cornerRadius = 10
cell.contentView.layer.borderWidth = 1.0
cell.contentView.layer.borderColor = UIColor.blue.cgColor
cell.backgroundColor = UIColor.white
cell.layer.shadowColor = UIColor.gray.cgColor
cell.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
cell.layer.shadowRadius = 2.0
cell.layer.shadowOpacity = 1.0
cell.layer.masksToBounds = false
cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: cell.contentView.layer.cornerRadius).cgPath
return cell
}
}
Here the code for the layout. I think I made mistake in the height or weight..
extension SearchViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let bounds = collectionView.bounds
let heigtVal = self.view.frame.height
let widthVal = self.view.frame.width
let cellsize = (heigtVal < widthVal) ? bounds.height/2: bounds.width/2
return CGSize(width: cellsize - 10, height: cellsize - 10)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
If the width of the cell or the contents of the cells is such that the collectionView cannot accommodate be able to see them within the visible portion of the screen, the collection view automatically places next to them. You have to scroll your collecctionView to see your next column.
If you want to see two columns within the visible screen portion, pls follow the steps:
Firstly place all your views inside a StackView and align them vertically.
Then set the right width & height constraints to your imageView to maintain the aspect ratio for your requirements. I have set like below just to demonstrate:
Along with the above constraints, I have also set the cell size to automatic in Storyboard itself rather than doing programmatically. Removed sizeForItemAt indexPath from code
:
Finally, after following all the above steps the output looked like this
I know some related questions already there regarding this but I tried those before and still no luck.
Here is my problem on the following screenshot
My current screen shows a fixed cell size and it displays empty spaces for smaller contents and larger contents going over the cell with dots..
I wanted like below
it should match the width of the product category name content.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.catCollectionView{
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
DispatchQueue.main.async {
catCell.configureCell()
let catCountInt = self.catCountArray[indexPath.row]
catCell.catCountLabel.text = String(catCountInt)
catCell.catNameLabel.text = self.categoryNameArray[indexPath.row]
catCell.catCountLabel.sizeToFit()
catCell.catNameLabel.sizeToFit()
}
return catCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
catCell.catNameLabel.text = self.categoryNameArray[indexPath.item]
catCell.catNameLabel.sizeToFit()
let labelWidth = catCell.catNameLabel.frame.width + 10
print("frame width: \(labelWidth)")
return CGSize(width: labelWidth, height: 21)
}
}
Maybe I'm missing a simple thing here but I couldn't quite figure out at this moment. Please help me and sorry for my strange English.
Let's imagine you are using a simple UICollectionViewCell subclass with constraints set up correctly in storyboard (label is pinned to all four sides of its superview) like this:
class CategoryCell: UICollectionViewCell {
#IBOutlet var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
layer.borderWidth = 1
}
}
Then you can simply let auto layout determine the cells' sizes:
class CollectionViewController: UICollectionViewController {
let categories = [
"All Products",
"Fresh",
"Health & Beauty",
"Beverages",
"Home & life"
]
private var flowLayout: UICollectionViewFlowLayout? {
return collectionViewLayout as? UICollectionViewFlowLayout
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .white
flowLayout?.sectionInset = .init(top: 15, left: 15, bottom: 15, right: 15)
flowLayout?.sectionInsetReference = .fromSafeArea
flowLayout?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
DispatchQueue.main.async {
self.flowLayout?.invalidateLayout()
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoryCell", for: indexPath) as! CategoryCell
cell.nameLabel.text = categories[indexPath.row]
return cell
}
}
Result:
Instead of dequeuing another cell in collectionView(_:layout:sizeForItemAt:), you can simply calculate the width of categoryName using size(withAttributes:) on the categoryName, i.e.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = self.categoryNameArray[indexPath.row] {
let cellWidth = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:14.0)]).width + 10.0
return CGSize(width: cellWidth, height: 21.0)
}
in attributes, give whatever font you want the categoryName to be.
I have a collectionView with 2 sections. The frame of the first section will depend on the number of cells inside of it. Since the total number of cells (firstSectionData.count) in the first section will vary I need to find the CGPoint for the Second Section. I specifically need to find out the x/y values of where the second section begins.
The frame will also be suffice because I can extract them from there. How can I find this out?
var cv: UICollectionView!
let firstSectionCell = "FirstSectionCell"
let secondSectionCell = "SecondSectionCell"
let secondSectionHeaderView = "SecondSectionHeaderView"
let firstSectionData = [String]()
let secondSectionData = [String]()
let cellHeight: CGFloat = 50
let headerHeight: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0)
layout.scrollDirection = .horizontal
cv = UICollectionView(frame: view.frame, collectionViewLayout: layout)
cv.delegate = self ; cv.dataSource = self
cv.register(FirstSectionCell.self, forCellWithReuseIdentifier: firstSectionCell)
cv.register(SecondSectionCell.self, forCellWithReuseIdentifier: secondSectionCell)
cv.register(SecondSectionHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: secondSectionHeaderView)
view.addSubview(cv)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0{
return firstSectionData.count // varies
}
return secondSectionData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0{
return firstSectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: self.firstSectionCell, for: indexPath) as! FirstSectionCell
}
return secondSectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: self.secondSectionCell, for: indexPath) as! SecondSectionCell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: self.cellHeight) // height is 50
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView: UICollectionReusableView?
if kind == UICollectionElementKindSectionHeader{
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: self.secondSectionHeaderView, for: indexPath) as! SecondSectionHeaderView
headerView = header
}
return headerView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0{
return CGSize.zero // the first section doesn't have a header
}
return CGSize(width: view.frame.width, height: self.headerHeight) // header height is 60 for the second section
}
This type of information is kept by the collection view's layout manager. The collection view provides two convenience methods:
layoutAttributesForItem(at:)
and:
layoutAttributesForSupplementaryElement(ofKind:at:)
These return a value of UICollectionViewLayoutAttributes?. This in turn has properties such as frame and others.
If you want the frame of a specific item in a collection view you would do:
let frame = collectionView.layoutAttributesForItem(at: someIndexPath)?.frame
If you want the frame of a section header in a collection view you would do:
let frame = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionElementKindSectionHeader, at: someSectionIndexPath)
To get the position of a UICollectionViewCell relative to the view, in which it's located (e.g. relative to your visible area):
if let theAttributes = collectionView.layoutAttributesForItem(at: IndexPath(item: 1, section: 0)) {
let cellFrameInSuperview = collectionView.convert(theAttributes.frame, to: collectionView.superview)
print("X of Cell is: \(cellFrameInSuperview.origin.x)")
}
So, I am right now making an app with a collection view layout and was wondering how to go about designing it to make it look nice. I found a picture online that I would like it to look like and was wondering how I should go about making it.
Here is the picture:
The collection view I would like to replicate is the one in the middle.
Here is how my current cells look like:
My Cells after the first answer:
This is my current code for configuring the cells:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "channelCell", for: indexPath) as? ChannelCollectionViewCell
let channel = channels[indexPath.row]
// Configure the cell
cell?.configureCell(channel: channel)
return cell!
}
My guess is there would be 2 main things to making the deisgn I want which brings me to two specific questions.
How do I make a cell have rounded corners?
How do I space a cell from the side of screen and reduce gaps between cells?
ViewController Class
class YourClass: UIViewController {
//MARK:-Outlets
#IBOutlet weak var yourCollectionView: UICollectionView!
//Mark:-Variables
var cellWidth:CGFloat = 0
var cellHeight:CGFloat = 0
var spacing:CGFloat = 12
var numberOfColumn:CGFloat = 2
//MARK:-LifeCycle
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
yourCollectionView.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
if let flowLayout = yourCollectionView.collectionViewLayout as? UICollectionViewFlowLayout{
cellWidth = (yourCollectionView.frame.width - (numberOfColumn + 1)*spacing)/numberOfColumn
cellHeight = 100 //yourCellHeight
flowLayout.minimumLineSpacing = spacing
flowLayout.minimumInteritemSpacing = spacing
}
}
extension YourClass:UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? YourCollectionViewCell{
//Configure cell
return cell
}
return UICollectionViewCell()
}
}
extension YourClass:UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: cellWidth, height: cellHeight)
}
}
CollectionViewCell Class
class YourCollectionViewCell:UICollectionViewCell{
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius = 10 //customize yourself
self.layer.masksToBounds = true
}
}