how to properly single out cell accessory view - swift

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
}

Related

How to purge tableview cells before cellforRowAt is called?

I have the following code
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
//creating a cell using the custom class
if (indexPath.row == 0 && indexPath.section == 0) {
var cell:UITableViewCell;
cell = tableView.dequeueReusableCell(withIdentifier: "book", for: indexPath)
cell.selectionStyle = UITableViewCell.SelectionStyle.none;
self.ref.observe(DataEventType.value, with: { (snapshot) in
self.ref.child("featured").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let title = value?["title"] as? String ?? ""
self.featured = value?["url"] as? String ?? ""
let label = UILabel(frame: CGRect(x: 0, y: 0, width: cell.frame.size.width, height: 24))
label.numberOfLines = 0
label.font = UIFont(name: "CeraPro-Medium", size: 20);
label.textColor = UIColor.black
label.text = NSLocalizedString(" Free Daily Pick", comment: "featured")
label.adjustsFontForContentSizeCategory = true
cell.addSubview(label)
if let image = value?["image"] as? String, image.isEmpty {
print("String is nil or empty.")
} else {
if let image = value?["image"] {
let imageView = UIImageView();
imageView.frame = CGRect(x: 0,
y: 75,
width: cell.frame.size.width,
height: 150)
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = false
imageView.sd_setImage(with: URL(string: image as! String), placeholderImage: nil)
cell.addSubview(imageView)
let label = UILabel(frame: CGRect(x: 10,
y: cell.frame.size.height-60,
width: cell.frame.size.width,
height: 50));
label.textColor = UIColor.black
label.backgroundColor = UIColor.init(red: 1, green: 1, blue: 1, alpha: 0.5)
label.font = UIFont(name: "CeraPro-Bold", size: 16)
label.text = " \(title)"
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: 150);
view.tag = 123
view.addSubview(imageView)
view.addSubview(label)
cell.contentView.addSubview(view)
}
}
// ...
}) { (error) in
print(error.localizedDescription)
}
})
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "book", for: indexPath) as! BookTableViewCell
cell.cellIndex = indexPath
cell.dataSource = self
cell.delegate = self
cell.backgroundColor = UIColor.white
var books = [Book]();
let count = self.enterpriseBooks.count;
if count > 0 && indexPath.section <= self.enterpriseBooks_2.count {
books = self.enterpriseBooks_2[indexPath.section - 1]!;
}
if (indexPath.section == (count + 1)) {
books = nytbooks;
} else if (indexPath.section == (count + 2)) {
books = trendingbooks;
} else if (indexPath.section == (count + 3)) {
books = newbooks
}
if (books.count > 0) {
if (cell.collectionView === nil) {
cell.addCollectionView();
cell.collectionView.reloadData()
}
}
return cell
}
}
Here for section 0, row 0 - I show a featured image
For section 1, 2, 3 - I show a horizontal collection view of book images.
What's happening is by using dequeue function to get the table view cell, the table view is caching some table view cell content which overlaps each other. Is there a way to purge table view cells before cellForRowAt is called?
Programmatically adding subviews to a cell seems a terrible idea to me. Try to use auto layout and xib files whenever possible.
Anyway, you theoretically could remove all the subviews from a dequeued cell and then adding them again.
for view in cell.subviews {
view.removeFromSuperview()
}
That said, there are better approaches to use a special cell in the first position, like using different cell types.
well, to handle caching data you have to handle all else cases to assign appropriate values to UI Elements in case of nil/ failure scenarios. For an instance you have to assign nil to UIImageView's image property if image url is nil/ empty like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
//creating a cell using the custom class
if (indexPath.row == 0 && indexPath.section == 0) {
var cell:UITableViewCell;
cell = tableView.dequeueReusableCell(withIdentifier: "book", for: indexPath)
cell.selectionStyle = UITableViewCell.SelectionStyle.none;
self.ref.observe(DataEventType.value, with: { (snapshot) in
self.ref.child("featured").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let title = value?["title"] as? String ?? ""
self.featured = value?["url"] as? String ?? ""
let label = UILabel(frame: CGRect(x: 0, y: 0, width: cell.frame.size.width, height: 24))
label.numberOfLines = 0
label.font = UIFont(name: "CeraPro-Medium", size: 20);
label.textColor = UIColor.black
label.text = NSLocalizedString(" Free Daily Pick", comment: "featured")
label.adjustsFontForContentSizeCategory = true
cell.addSubview(label)
let imageView = UIImageView();
if let image = value?["image"] as? String, image.isEmpty {
print("String is nil or empty.")
imageView.image = nil
// same goes for the label
} else {
if let image = value?["image"] {
imageView.frame = CGRect(x: 0,
y: 75,
width: cell.frame.size.width,
height: 150)
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = false
imageView.sd_setImage(with: URL(string: image as! String), placeholderImage: nil)
cell.addSubview(imageView)
let label = UILabel(frame: CGRect(x: 10,
y: cell.frame.size.height-60,
width: cell.frame.size.width,
height: 50));
label.textColor = UIColor.black
label.backgroundColor = UIColor.init(red: 1, green: 1, blue: 1, alpha: 0.5)
label.font = UIFont(name: "CeraPro-Bold", size: 16)
label.text = " \(title)"
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: 150);
view.tag = 123
view.addSubview(imageView)
view.addSubview(label)
cell.contentView.addSubview(view)
}
}
// ...
}) { (error) in
print(error.localizedDescription)
}
})
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "book", for: indexPath) as! BookTableViewCell
cell.cellIndex = indexPath
cell.dataSource = self
cell.delegate = self
cell.backgroundColor = UIColor.white
var books = [Book]();
let count = self.enterpriseBooks.count;
if count > 0 && indexPath.section <= self.enterpriseBooks_2.count {
books = self.enterpriseBooks_2[indexPath.section - 1]!;
}
if (indexPath.section == (count + 1)) {
books = nytbooks;
} else if (indexPath.section == (count + 2)) {
books = trendingbooks;
} else if (indexPath.section == (count + 3)) {
books = newbooks
}
if (books.count > 0) {
if (cell.collectionView === nil) {
cell.addCollectionView();
cell.collectionView.reloadData()
}
}
return cell
}
}
In all the nil cases you are doing nothing with related UI element and that allows dequeueReusability to retain old content. Just assign nil or default value to UI elements in else / nil case, it will resolve your issue

BackgroundView for section in the tableView

I use this function to add background view in my TableView when it's empty.
func emptyTableViewMessage(with message: String) {
let messageLabel: UILabel = UILabel(frame: CGRect(x: todoTableView.bounds.size.width/2, y: todoTableView.bounds.size.height / 2 , width: todoTableView.bounds.size.width, height: todoTableView.bounds.size.height))
messageLabel.text = message
messageLabel.textColor = UIColor.gray
messageLabel.font = UIFont(name: "Open Sans", size: 15)
messageLabel.textAlignment = .center
todoTableView.backgroundView = messageLabel
}
I call it in numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching {
if searchedData?.count == 0 {
emptyTableViewMessage(with: "Not found")
} else {
emptyTableViewMessage.backgroundView = .none
}
return (searchedData?.count)!
}
But if I have few section, For example one of them is empty, how Can I add this kind of backgroundView inside the section (not in the header, because I have different items in the header)
Many thanks
First, you could created an empty cell prototype, this one will be use when there is no data to show. Then let's modify your emptyTableViewMessage method to return a UILabel.
func emptyTableViewMessage(with message: String) -> UIView {
let messageLabel: UILabel = UILabel(frame: CGRect(x: table.bounds.size.width/2, y: table.bounds.size.height / 2 , width: table.bounds.size.width, height: table.bounds.size.height))
messageLabel.text = message
messageLabel.textColor = UIColor.gray
messageLabel.font = UIFont(name: "Open Sans", size: 15)
messageLabel.textAlignment = .center
return messageLabel
}
In numberOfRowsInSection when there is no data to show lets return 1 (to display the Not Found text). If there is data to show let's return how many rows.
guard let dataFound = searchedData?.count else { return 1 }
return dataFound > 0 ? dataFound : 1
Finally in cellForRowAt you dequeue the appropriate prototype cell based on searchData?.count.
let notFound = searchData?.count > 0 ?? false
guard let cell = tableView.dequeueReusableCell(withIdentifier: notFound ? "NotFoundCellId" : "CellId") else { return UITableViewCell() }
if notFound {
cell.backgroundView = emptyTableViewMessage(with: "Not Found")
} else {
cell.textLabel?.text = countries[indexPath.row]
}
I quickly created a small project to test this and seems to work fine, you can check it at Github. The project doesn't manage the data how it should be done for a production code, the search bar doesn't displays well on simulator for iPhone X and newer, etc.; it's just a quick sample on how to display the Not Found text in a table view with many sections so you can have an idea how to do it.
Some screenshots:
No filter
Filtered by "ica"
Filtered by "an"
Hope this helps you!

UICollection Cells are mixed in 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.

How to make a Progress View in a TableView?

I'm trying to make a Progress View on a TableView.
I succeeeded to create progressview, but if animation is true bar disappears, and if is false bar is full... i can't understand why.
This is my code:
here is my appending method:
if formazione == formazione {
guestInfoList.append("Formazione: \(formazione)%")
}
and here the full dequeue function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var countdowncell = tableView.dequeueReusableCell(withIdentifier: "countdowncell", for: indexPath)
//countdown
if indexPath.row == 9{
countdowncell.textLabel?.text = calcolaCountdown()
}else{
countdowncell.textLabel?.text = guestInfoList[indexPath.row]
}
//Creazione Progress View
if indexPath.row == 12 {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
let progressView = UIProgressView(progressViewStyle: .default)
//setting height of progressView
let transform = CGAffineTransform(scaleX: cell.frame.size.width, y: cell.frame.size.height)
progressView.transform = transform
cell.contentView.addSubview(progressView)
//**//inserisci valore formazione
progressView.setProgress(Float(formazione), animated: false)
}
return countdowncell
}
Ps: and if i want to put the progress View on the right Side of the cell?
Edit
Finally i made it! Thank you matt!!
if indexPath.row == 12 {
var cell = tableView.dequeueReusableCell(withIdentifier: "formprogresscell", for: indexPath)
let progressView = UIProgressView(progressViewStyle: .default)
//setting height of progressView
progressView.frame = CGRect(x: 230, y: 20, width: 130, height: 130)
progressView.progress += 0.5
progressView.rightAnchor.accessibilityActivate()
//**//inserisci valore formazione
progressView.setProgress(Float(formazione), animated: true)
progressView.progressTintColor = UIColor.red
cell.textLabel?.text = "Formazione: \(formvalue)%"
cell.contentView.addSubview(progressView)
}
The problem is what you do with the progress view after creating it:
let progressView = UIProgressView(progressViewStyle: .default)
You are failing to give the progressView any frame. Therefore it doesn't have one. In particular, it has no width. Therefore it is invisible.
Giving it a frame will solve both your problems: you'll give the view a width, and you'll give it an origin so you can put it where you want inside the content view. (But in fact you should be using autolayout, not the frame.)

UITableViewCell accessoryView not showing in iOS9

Trying to display a number on the right side of a table view cell in a label as an 'accessoryView', but the cell only displays the text of cell label. What am I doing wrong?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let item = items[indexPath.section][indexPath.row]
let cell = UITableViewCell(style:.Default,reuseIdentifier:nil)
if let label = cell.textLabel {
label.text = item.name
}
cell.selectionStyle = .None
let label = UILabel()
label.textColor = UIColor.blackColor()
label.text = String(item.count)
label.textAlignment = .Right
cell.accessoryView = label
return cell
}
I think frame for UILabel is missing.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 45, height: 45))
label.textColor = UIColor.blackColor()
label.text = String("8")
label.textAlignment = .Right
cell.accessoryView = label
let label = UILabel()
label.textColor = UIColor.blackColor()
label.text = String(item.count)
label.textAlignment = .Right
label.sizeToFit() // ADD THIS LINE
cell.accessoryView = label
should check if you have this part of code on your tableView
tableView.editing = true
Or
tableView.setEditing(true, animated:animated)
accessoryView will be hidden if editing mode is true