UICollection Cells are mixed in swift - swift

I'm trying to generate cells and put labels inside of it. However, when i scroll down labels got mixed between cells. Here is my code and i'm trying to solve it.
let lblTitle = UILabel()
let lblMetro = UILabel()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath) as? UICustomCollectionViewCell
if indexPath.row == 0 {
lblTitle.frame = CGRect(x: 0, y: 0, width: 195, height: 40)
lblTitle.font = UIFont.systemFont(ofSize: 14)
lblTitle.textColor = UIColor.white
lblTitle.backgroundColor = colorLiteral(red: 0.2122299671, green: 0.4379466176, blue: 0.8993332386, alpha: 1)
lblTitle.text = " 1”
cell?.contentView.addSubview(lblTitle)
}
if indexPath.row == 1 {
lblMetro.frame = CGRect(x: 55, y: 290, width: 100, height: 20)
lblMetro.font = UIFont.boldSystemFont(ofSize: 17)
lblMetro.textColor = colorLiteral(red: 0, green: 0.3117707968, blue: 0.5609284043, alpha: 1)
lblMetro.text = “2”
cell?.contentView.addSubview(lblMetro)
}
return cell ?? UICollectionViewCell()
}
}

Here cells are dequeued
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath) as? UICustomCollectionViewCell
so you might get a 1 with previously added label , you need to clear them after dequeue , it would be messy but it's better to isolate the vc's labels from the cells 1 so add them inisde the cell configure or make them an outlets , to remove give them a tag and after the above line do
cell.contentView.subviews.forEach {
if $0.tag == 200 {
$0.removeFromSuperview()
}
}

Not optimised but this might solve it, remove the subview from superview before adding it:
cell?.contentView.lblTitle.removeFromSuperview()
cell?.contentView.addSubview(lblTitle)
And:
cell?.contentView.lblMetro.removeFromSuperview()
cell?.contentView.addSubview(lblMetro)

I suggest using a very rarely used method of UICollectionViewCell or UITableViewCell prepareForReuse.
In definition of UICustomCollectionViewCell insert the function:
class UICustomCollectionViewCell: UICollectionViewCell {
func prepareForReuse() {
// This method is immediately called when a cell is about to be dequeued.
super.prepareForReuse()
if let view = contentView.viewWithTag(100) {
view.removeFromSuperView()
}
if let view = contentView.viewWithTag(101) {
view.removeFromSuperView()
}
}
}
Then give tags to the labels
lblMetro.tag = 100
lblTitle.tag = 101
This solution is efficient if you only use a limited labels and cells. For a more generic approach create labels dynamically and share the tag. In the prepareForReuse() just remove subview with that tag.

Related

tableView Cell Labels Stack When I Scroll

I have a custom tableView, entirely created in code. IE, the cells need to be in code too.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inventoryCell", for: indexPath)
let namelabel = UILabel()
namelabel.frame = CGRect(x: 145, y: 10 , width: 200, height: 30 )
namelabel.text = itemToShow.name
namelabel.font = UIFont.boldSystemFont(ofSize: 20)
namelabel.backgroundColor = .clear
cell.addSubview(namelabel)
let detailLabel = UILabel()
detailLabel.frame = CGRect(x: 145, y: 50 , width: 200, height: 50 )
detailLabel.text = itemToShow.detail
detailLabel.backgroundColor = .clear
cell.addSubview(detailLabel)
let inventoryImage = UIImageView()
inventoryImage.frame = CGRect(x: 10, y: 10, width: 130, height: 130)
inventoryImage.image = UIImage(named: "emptyInventorySlot")
cell.addSubview(inventoryImage)
return cell
}
It works great, and you can see it loads perfectly. The top image is the load, the bottom image is once I scroll to the bottom. You can see the text labels seem to all stack on top of each other.
I would not recommend doing it this way.
It's better to move the code that creates namelabel, detailLabel and inventoryImage to an inventoryCell class derived from UITableViewCell.
But you can make it work.
Since table cells are reused and the reused cells already contain the created subviews, you need to either remove the subviews or treat cells with already created subviews differently.
And you should place your labels in the contentView of your UITableCellView.
To make your code work you can do this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inventoryCell", for: indexPath)
// remove any subviews from contentView from recycled cells
cell.contentView.subviews.forEach{ $0.removeFromSuperview() }
let namelabel = UILabel()
namelabel.frame = CGRect(x: 145, y: 10 , width: 200, height: 30 )
namelabel.text = itemToShow.name
namelabel.font = UIFont.boldSystemFont(ofSize: 20)
namelabel.backgroundColor = .clear
// add any label to contentView of the cell
cell.contentView.addSubview(namelabel)
let detailLabel = UILabel()
detailLabel.frame = CGRect(x: 145, y: 50 , width: 200, height: 50 )
detailLabel.text = itemToShow.detail
detailLabel.backgroundColor = .clear
// add any label to contentView of the cell
cell.contentView.addSubview(detailLabel)
let inventoryImage = UIImageView()
inventoryImage.frame = CGRect(x: 10, y: 10, width: 130, height: 130)
inventoryImage.image = UIImage(named: "emptyInventorySlot")
// add any label to contentView of the cell
cell.contentView.addSubview(inventoryImage)
return cell
}
Better would be:
create a UITableViewCell subclass
place your label placement and configuration there
take care of cell reuses, override prepareForReuse() to support cell reuse
register this class as a table view cell for your tableView
cast your dequeued cell to your UITableViewCell subclass

how to properly single out cell accessory view

So my goal is to properly separate the cells accessory. Here is my first set of cells when the segment control is at the first index. The accessory type is a normal disclosure indicator.
Now when I switch the value of the segment index, i set my cells with a custom accessory view.
Now the issue is when I switch back to the first segment, the custom accessory view comes over to the first 2 cells as well like so :
I just want to figure out how I can prevent this from happening and keep the cell accessories properly separated. I will attach my necessary code for the issue.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.CellDetails.studentDashCell, for: indexPath)
let cellImage = UIImage(systemName: "checkmark.seal.fill")
let cellImageView = UIImageView(image: cellImage)
cellImageView.tintColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)
cellImageView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
if selectorOfEvents.selectedSegmentIndex == 0 {
cell.textLabel?.font = UIFont(name: Constants.AppFonts.consistentFont, size: 22)
cell.textLabel?.text = gothereEvents[indexPath.row].eventName
cell.accessoryType = .disclosureIndicator
} else if selectorOfEvents.selectedSegmentIndex == 1 {
cell.textLabel?.font = UIFont(name: Constants.AppFonts.menuTitleFont, size: 22)
cell.textLabel?.text = gotherePurchasedEventNames[indexPath.row].purchasedEventName
cell.accessoryView = cellImageView
}
return cell
}
The cellForRowAt() method. ^
#IBAction func eventsSegmented(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
navigationItem.title = "Events"
tableView.reloadData()
} else {
navigationItem.title = "Purchased Events"
tableView.reloadData()
}
}
The IBAction func for the segmented control. Thanks in advance.
My suspicion is that the accessory view / type is getting carried over on some dequeued cells. You should explicitly set both the accessoryView and cell.accessoryType type in both cases:
if selectorOfEvents.selectedSegmentIndex == 0 {
cell.textLabel?.font = UIFont(name: Constants.AppFonts.consistentFont, size: 22)
cell.textLabel?.text = gothereEvents[indexPath.row].eventName
cell.accessoryType = .disclosureIndicator
cell.accessoryView = nil //<-- here
} else if selectorOfEvents.selectedSegmentIndex == 1 {
cell.textLabel?.font = UIFont(name: Constants.AppFonts.menuTitleFont, size: 22)
cell.textLabel?.text = gotherePurchasedEventNames[indexPath.row].purchasedEventName
cell.accessoryView = cellImageView
cell.accessoryType = .none //<-- here
}

UICollectionView Header title change

I have a UICollectionView with 4 custom UICollectionViewCells. In the header of the UICollectionView there's a UISegmentedControl. My goal is to change the header UILabel that plays the role of a title. Right now if the segmented control value had been changed, the cells are reloaded and the title should be switched, but it overlaps with the first title. I can't figure out why.
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionElementKindSectionHeader {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderDiscoverVC", for: indexPath) as! HeaderDiscoverVC
headerView.frame = CGRect(x: 0, y: 0, width: collectionView.frame.width, height: 30)
headerView.backgroundColor = UIColor.hex("d9e2e7")
let label = UILabel(frame: CGRect(x: 16, y: 0, width: headerView.frame.width, height: 30))
switch segReusableIdentifier {
case "Reply":
label.text = "Reply"
case "Media":
label.text = "Media"
case "Likes":
label.text = "Likes"
case "Comments":
label.text = "Comments"
default:
label.text = ""
}
label.font = UIFont(name: Fonts.OpenSans_Bold, size: 16)
label.textColor = UIColor.hex("8a9da6")
headerView.addSubview(label)
return headerView
}
fatalError("Unexpected element kind")
}
The problem is with the way you are adding the label to your header view.
You should put the headerView.addSubview(label) to your HeaderDiscoverVC Class. Also set the colour and font to the same class.
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionElementKindSectionHeader {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderDiscoverVC", for: indexPath) as! HeaderDiscoverVC
headerView.frame = CGRect(x: 0, y: 0, width: collectionView.frame.width, height: 30)
// MOVE THE COMMENTED LINE TO YOUR HeaderDiscoverVC
//headerView.backgroundColor = UIColor.hex("d9e2e7")
headerView.label.frame = CGRect(x: 16, y: 0, width: headerView.frame.width, height: 30)
switch segReusableIdentifier {
case "Reply":
headerView.label.text = "Reply"
case "Media":
headerView.label.text = "Media"
case "Likes":
headerView.label.text = "Likes"
case "Comments":
headerView.label.text = "Comments"
default:
headerView.label.text = ""
}
// MOVE THE COMMENTED LINES TO YOUR HeaderDiscoverVC
//label.font = UIFont(name: Fonts.OpenSans_Bold, size: 16)
//label.textColor = UIColor.hex("8a9da6")
//headerView.addSubview(label)
return headerView
}
fatalError("Unexpected element kind")
}
Try and share your results
You are adding label programmatically to the headerView which should be removed before adding again. dequeueReusableSupplementaryView do not remove programmatically added subviews.
In your code:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionElementKindSectionHeader {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderDiscoverVC", for: indexPath) as! HeaderDiscoverVC
headerView.frame = CGRect(x: 0, y: 0, width: collectionView.frame.width, height: 30)
headerView.backgroundColor = UIColor.hex("d9e2e7")
///// Add below code to remove all subviews first before adding any new subview programmatically
for label in headerView.subviews {
if let mylabel = label as? UILabel {
mylabel.removeFromSuperview()
}
}
////////////////
let label = UILabel(frame: CGRect(x: 16, y: 0, width: headerView.frame.width, height: 30))
switch segReusableIdentifier {
case "Reply":
label.text = "Reply"
case "Media":
label.text = "Media"
case "Likes":
label.text = "Likes"
case "Comments":
label.text = "Comments"
default:
label.text = ""
}
label.font = UIFont(name: Fonts.OpenSans_Bold, size: 16)
label.textColor = UIColor.hex("8a9da6")
headerView.addSubview(label)
return headerView
}
fatalError("Unexpected element kind")
}
Better approach will be to keep the label in HeaderDiscoverVC and use it in the code as:
headerView.label.text = "Your data"
In this way you don't have to remove subViews programmatically.

How to apply custom styling to NSTableHeaderView?

So I am going for a custom looking NSTableView. I've already successfully subclassed NSTableRowView and NSTextFieldCell to achieve the look I'm going for, however I'm struggling of getting rid of the default styling for the header. I seem to be able to tweak its frame, however I'm not sure where the rest of the default styling is coming from.
As you see on the screenshot the red area is the increased frame of the headerView. I'm using its CALayer to set the colour, however how to change the contents inside is beyond me...
Here's what I'm doing in the viewDidLoad of my ViewController
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.wantsLayer = true
tableView.headerView?.frame = NSMakeRect(0, 0, (tableView.headerView?.frame.width)!, 32.00)
tableView.headerView?.wantsLayer = true
tableView.headerView?.layer?.backgroundColor = NSColor.red.cgColor
}
I've also tried subclassing NSTableHeaderView, however this class seems to be extremely limited in terms of the customizations I can make...
Any help would be appreciated?
The table view is view based but the header isn't and the header cells still are class NSTableHeaderCell. Use NSTableColumn's property headerCell. You can set the cell's properties like attributedStringValue and backgroundColor or replace the cells by instances of a subclass of NSTableHeaderCell and override one of the draw methods.
Play around with this to get inside the header.
Remember to except the answer if it works for you.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//Color for the header.
let topColor = UIColor(red: (70/255.0), green: 000/255.0, blue: 000/255.0, alpha: 255)
//Location of label.
let locationOfLabel = self.view.frame.width
let headerView:UIView = UIView()
//Locating the text in the label
let title = UILabel(frame: CGRect(x: 0, y: 30, width: locationOfLabel, height: 21))
title.textAlignment = .center
//Changing the title in the label per the default.
let defaults:UserDefaults = UserDefaults.standard
defaults.synchronize()
let cardSelector = defaults.object(forKey: "selectorKeyID") as! Int
switch (cardSelector) {
case 0: title.text = "Personal"
break
case 1: title.text = "Saved"
break
case 2: title.text = "Favorite"
break
case 3: title.text = "Grouped"
break
default:
break
}
//Coloring the text in the label
//Add the label
title.textColor = UIColor.gray
headerView.addSubview(title)
//Adding a button to the header.
let closeBttn = UIButton(type: UIButtonType.system) as UIButton
closeBttn.frame = CGRect(x: 0, y: 30, width: 90, height: 27)
closeBttn.setTitle("Close", for: UIControlState())
closeBttn.setTitleColor(buttonColor, for: UIControlState())
closeBttn.titleLabel?.font = UIFont.systemFont(ofSize: 19, weight: UIFontWeightMedium)
closeBttn.addTarget(self, action: #selector(MainTableViewController.close), for: UIControlEvents.touchUpInside)
headerView.addSubview(closeBttn)
let menuButton = UIButton(type: UIButtonType.system) as UIButton
menuButton.frame = CGRect(x: locationOfLabel-53, y: 30, width: 27, height: 27)
menuButton.setBackgroundImage(UIImage(named: "VBC Menu4.png"), for: UIControlState())
menuButton.addTarget(self, action: #selector(MainTableViewController.menuButton), for: UIControlEvents.touchUpInside)
headerView.addSubview(menuButton)
//Coloring the header
headerView.backgroundColor = topColor
//Rounding the corners.
headerView.layer.cornerRadius = 10
headerView.clipsToBounds = true
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 70.0
}

UICollectionView cell views overlapping

I have cells overlapping like so:
my cellForItemAtIndexPath is as such:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as UICollectionViewCell
cell.backgroundColor = UIColor(red: 27.0/255.0, green: 38.0/255.0, blue: 52.0/255.0, alpha: 1.0)
let textFrame = CGRect(x: 0, y: cell.frame.height * 0.30, width: cell.frame.width, height: cell.frame.height)
var textLabel: UILabel! = UILabel(frame: textFrame)
textLabel.font = UIFont(name:"Helvetica-Light", size: 14.0)
textLabel.textAlignment = .Center
textLabel.textColor = UIColor.whiteColor()
println(categoryArray[indexPath.row].category)
textLabel.text = categoryArray[indexPath.row].category
var cellImage = UIImage(named: categoryArray[indexPath.row].catImage)//Array(Array(model.categories.values)[cellCount])[1]
let imageSize = cell.frame.height * 0.45
let imageView = UIImageView(image: cellImage as UIImage?)
imageView.frame = CGRect(x: (cell.frame.width / 2) - (imageSize / 2), y:cell.frame.height * 0.15, width: imageSize, height: imageSize)
var bottomBorder: UIView = UIView(frame:CGRectMake(0, cell.frame.height - 1.0, cell.frame.width, 5));
//bottomBorder.backgroundColor = UIColor(rgba: Array(Array(model.categories.values)[cellCount])[0] as String)
bottomBorder.backgroundColor = UIColor(rgba: "#A64259")
cell.addSubview(imageView)
cell.addSubview(bottomBorder)
cell.addSubview(textLabel)
cellCount++
return cell
}
I understand that it reuses the cells, great idea...except how do I prevent the cell text from overlapping?
EDIT - POTENTIAL SOLUTION #1
Since these subviews were continually being modified I figured, what if I just dumped them and created new ones so I used:
for view in cell.subviews {
view.removeFromSuperview()
}
And that seemed to do the trick. I suspect this has a little more overhead than just modifying the values of the specific elements in the subviews. Will investigate further.
The reason it's happening is because the cells are being reused and you end up adding the image as a subview multiple times to the same UICollectionViewCell object. You can make a custom class that extends UICollectionViewCell so that you can hold onto the imageView that you add.
class ImageCell : UICollectionViewCell {
private(set) var imageView : UIImageView?
private(set) var textLabel : UILabel?
func setImage(image: UIImage?) {
if self.imageView == nil {
let imageSize = cell.frame.height * 0.45
self.imageView = UIImageView()
self.imageView.frame = CGRect(x: (self.frame.width / 2) - (imageSize / 2), y:self.frame.height * 0.15, width: imageSize, height: imageSize)
self.addSubview(imageView!)
}
imageView!.image = image
}
func setLabel(text: String) {
if self.textLabel == nil {
let textFrame = CGRect(x: 0, y: self.frame.height * 0.30, width: self.frame.width, height: self.frame.height)
self.textLabel = UILabel(frame: textFrame)
textLabel.font = UIFont(name:"Helvetica-Light", size: 14.0)
textLabel.textAlignment = .Center
textLabel.textColor = UIColor.whiteColor()
}
textLabel.text = text
}
}
Then in your cellForItemAtIndexPath:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as ImageCell
var cellImage = UIImage(named: categoryArray[indexPath.row].catImage)
cell.setImage(cellImage)
cell.setLabel(categoryArray[indexPath.row].category)
Obviously you would have to customize it to get the same layout, but that should get you started.
Well, since it reuses the cell, and since you are adding subviews to the cell on every call, you will end up with multiple overlapping views in the same cell!
Instead, you may want to add the subviews only once, tag them, then on getting called to provide a cell dequeue one, retrieve the subviews using their tags, and set their properties as needed.