How to purge tableview cells before cellforRowAt is called? - swift

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

Related

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
}

Table View Lag (even after removing gesture recognizers)

Currently, I have a table view that lags when there are multiple cells. When there is only one cell, it performs smoothly; however, when multiple cells are populated, the table view has a slow feeling, sometimes stutters, and is not as smooth. I removed the gesture recognizers and instead changed it to didSelectRowAt, however there is still lag within the cells. Here is the code for the when the cells are loaded :
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if queryComplete == true {
if displayConvo == true {
let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell", for: indexPath as IndexPath) as! messageTableViewCell
cell.profileImage.image = nil
cell.selectionStyle = UITableViewCell.SelectionStyle.none
var convoContent: messagesViewController.Convo
convoContent = convoList[indexPath.row]
convoContent.itemHeroID = "\(String(describing: convoContent.convoID))"
cell.name.heroID = "\(String(describing: convoContent.convoID))"
cell.lastMessage.text = convoContent.lastMessage
cell.backgroundColor = UIColor.clear
cell.postText.text = convoContent.postContent
if convoContent.revealedBool == true {
cell.profileImage.isHidden = false
cell.profileImage.heroID = "\(String(describing: convoContent.convoID))"
cell.name.text = convoContent.name
cell.timer.isHidden = true
if convoContent.profileImage == "nil" || convoContent.profileImage == nil {
let url = URL(string:"https://firebasestorage.googleapis.comasdjfjaisfdji")
let processor = RoundCornerImageProcessor(cornerRadius: cell.profileImage.frame.size.width / 2)
cell.profileImage.kf.setImage(with: url, placeholder: nil, options: [.processor(processor)])
}
else {
let url = URL(string: convoContent.profileImage!)
let processor = RoundCornerImageProcessor(cornerRadius: cell.profileImage.frame.size.width / 2)
cell.profileImage.kf.setImage(with: url, placeholder: nil, options: [.processor(processor)])
}
}
else {
cell.profileImage.isHidden = true
cell.timer.isHidden = false
cell.timer.isUserInteractionEnabled = false
cell.timer.heroID = "\(String(describing: convoContent.convoID))"
let postDuration = convoContent.timeOfDeletion! - Int(convoContent.time!)
let currentTime = Date().timeIntervalSince1970 * 1000
let difference = Int(round(Double(convoContent.timeOfDeletion! - Int(currentTime))))
let date = Date(timeIntervalSinceNow: TimeInterval(difference / 1000))
cell.name.text = String(timeToDelete(date: date as NSDate, numericDates: false))
let amountOfCircleCovered = (Double(((convoContent.timeOfDeletion!) - Int(currentTime))) / Double(postDuration)) * 100
var timerColor: UIColor?
switch amountOfCircleCovered {
case 0..<30:
timerColor = UIColor (red: 252/255, green: 110/255, blue: 81/255, alpha: 1)
case 30..<60:
timerColor = UIColor (red: 255/255, green: 215/255, blue: 0/255, alpha: 1)
case 60..<100:
timerColor = UIColor(red: 26/255, green: 152/255, blue: 252/255, alpha: 1.0)
default:
timerColor = UIColor(red: 26/255, green: 152/255, blue: 252/255, alpha: 1.0)
}
print(amountOfCircleCovered)
cell.timer.models = [ PieSliceModel(value: Double(100 - amountOfCircleCovered), color: UIColor(red: 220/255, green: 220/255, blue: 220/255, alpha: 1)),
PieSliceModel(value: Double(amountOfCircleCovered), color: timerColor!),
]
}
let lastMessageDate = Date(timeIntervalSince1970: TimeInterval(convoContent.timeOfLastMessage! / 1000))
cell.timeOfLastMessage.text = String(timeAgo(date: lastMessageDate as NSDate, numericDates: false))
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell", for: indexPath as IndexPath) as! messageTableViewCell
cell.lastMessage.text = "No new messages"
return cell
}
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell", for: indexPath as IndexPath) as! messageTableViewCell
print ("loading")
return cell
}
}
Additionally, I have tried moving some of the aspects of the cell so they can be reset each time by putting them in the cell file:
override func awakeFromNib() {
super.awakeFromNib()
profileImage.cornerRadius = profileImage.frame.size.width / 2
profileImage.clipsToBounds = true
profileImage.layer.borderColor = UIColor.white.cgColor
profileImage.layer.borderWidth = 1
timer.innerRadius = 0
timer.outerRadius = timer.frame.width / 2
timer.animDuration = 0.0
timer.referenceAngle = 270
timer.backgroundColor = UIColor.white
postText.layer.zPosition = 1
if !UIAccessibility.isReduceTransparencyEnabled {
glossyView.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .regular)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
//always fill the view
blurEffectView.frame = self.glossyView.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
glossyView.addSubview(blurEffectView) //if you have more UIViews, use an insertSubview API to place it where needed
} else {
glossyView.backgroundColor = .black
}
timer.selectedOffset = 0
glossyView.clipsToBounds = true
glossyView.cornerRadius = 12.0
glossyView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
messageBackground.clipsToBounds = true
messageBackground.cornerRadius = 12.0
messageBackground.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
// glossyView.roundCornersView(corners: [.topLeft, .topRight], radius: 12.0)
// messageBackground.roundCornersView(corners: [.bottomLeft,.bottomRight], radius: 12.0)
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepareForReuse() {
profileImage.image = nil
timer.isHidden = true
timer.models = []
}
}
And yet, when multiple cells are loaded, the table view still lags. I made sure there are no gesture recognizers as this link suggests and also the images are being loaded using a library so I am not sure why the cells still lag.
Essentially, the UI was dramatically slowing down the performance. I switched out the pie time CA Layer for png images of circles partially filled, and removed all Hero animations. As a result, both the app and tableview became much smoother.

Overriding the section row data in expand table view, when there are multiple sections of same type

I have created a Collapsible table view using sections and which in-turn contains a single row.Initially tableview has 4 sections and My last section has ability of adding 3rd section multiple times when the user taps on the last.
Now am facing an issue when the user expands the section and enters the data and after that when he expands the dynamically added section the same data appearing in the newly added only also and it is vice-versa if the user modifies any data on newly added section row then it is overriding the existing content in 3rd section too.
The code is as follows:
override func viewDidLoad() {
super.viewDidLoad()
//creating section items where 1st three are static and the third section will add dynamically when the last section tapped by the user.
tableMenuItems = [["sectionHeader": "Ship To","isCollapsed":true, "image" : "shipto.png" ],
["sectionHeader": "Ship From","isCollapsed":true,"image" : "shipfrom.png"],
["sectionHeader": "Package Details","isCollapsed":true,"image" : "package.png"],
["sectionHeader": "Add a Package","isCollapsed":true,"image" : "newaddpack.png"]]
}
func numberOfSections(in tableView: UITableView) -> Int {
return tableMenuItems.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let isCollapsed = tableMenuItems[section]["isCollapsed"] as! Bool
return isCollapsed ? 0 : 1
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let view = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tableView.frame.size.width, height: 120))
view.tag = 200 + section
view.backgroundColor = .white
let prefixIconBtn = UIButton.init(frame: CGRect.init(x: 12, y: 20, width: 48, height: 48))
prefixIconBtn.setImage(UIImage.init(named: tableMenuItems[section]["image"] as! String), for: .normal)
prefixIconBtn.tag = 500 + section
prefixIconBtn.addTarget(self, action: #selector (displayOrHideSection(_sender:)), for: .touchUpInside)
let titleLabel = UILabel.init(frame: CGRect.init(x: 75, y: 18, width: 250, height: 60))
titleLabel.tag = 100 + section
let sectionHeaderBtn = UIButton.init(frame: titleLabel.frame)
sectionHeaderBtn.addTarget(self, action: #selector (displayOrHideSection(_sender:)), for: .touchUpInside)
sectionHeaderBtn.tag = section
titleLabel.font = UIFont(name:"SFUIText-Regular", size: 20.0)
let plusOrMinus = UIButton.init(frame: CGRect.init(x: 340, y: 20, width: 38, height: 38))
plusOrMinus.tag = section
plusOrMinus.addTarget(self, action: #selector (displayOrHideSection(_sender:)), for: .touchUpInside)
titleLabel.text = tableMenuItems[section]["sectionHeader"] as? String
if titleLabel.text == "Add a Package" {
plusOrMinus.isHidden = true
}
if tableMenuItems[section]["isCollapsed"] as! Bool == true
{
plusOrMinus.setBackgroundImage(UIImage.init(named: "Log_plus"), for: .normal)
tableMenuItems[section]["isCollapsed"] = true
}
else
{
plusOrMinus.setBackgroundImage(UIImage.init(named: "Log_minus"), for: .normal)
tableMenuItems[section]["isCollapsed"] = false
}
view.addSubview(titleLabel)
view.addSubview(prefixIconBtn)
view.addSubview(sectionHeaderBtn)
view.addSubview(plusOrMinus)
plusOrMinus.tag = section
let seperatorLine = UILabel.init(frame: CGRect.init(x: 78, y: 78, width: tableView.frame.size.width, height: 1))
seperatorLine.backgroundColor = UIColor.lightGray
view.addSubview(seperatorLine)
return view
}
#objc func displayOrHideSection( _sender : AnyObject)
{
let section = _sender.tag
if section == 0
{
if tableMenuItems[0]["isCollapsed"] as! Bool == false
{
tableMenuItems[0]["isCollapsed"] = true
}
else
{
tableMenuItems[0]["isCollapsed"] = false
}
}
else if section == 1
{
if tableMenuItems[1]["isCollapsed"] as! Bool == false
{
tableMenuItems[1]["isCollapsed"] = true
}
else
{
tableMenuItems[1]["isCollapsed"] = false
}
}
else if section == 2
{
if tableMenuItems[2]["isCollapsed"] as! Bool == false
{
tableMenuItems[2]["isCollapsed"] = true
}
else
{
tableMenuItems[2]["isCollapsed"] = false
}
}
else if section! >= 3
{
if section! > 502
{
// to delete a dynamically added package
tableMenuItems.remove(at: section! - 500)
}
else if section! < 502
{
// adding a new package section when the user taps on Add a package section(Last section)
let titleLabel = self.logTable?.viewWithTag(100 + section!) as! UILabel
print(titleLabel)
// when add a package tapped
if titleLabel.text == "Add a Package"
{
tableMenuItems.insert(["sectionHeader": "Package Details","isCollapsed":true,"image" : "package.png"], at: tableMenuItems.count-1)
}
// when plus or minus button tapped on Add a package
else
{
if tableMenuItems[section!]["isCollapsed"] as! Bool == false
{
// expanding section
tableMenuItems[section!]["isCollapsed"] = true
tableMenuItems[section!]["image"] = "package.png"
}
else
{
// collapsing section
tableMenuItems[section!]["isCollapsed"] = false
tableMenuItems[section!]["image"] = "newdeletepackage.png"
}
}
}
}
logTable!.reloadData()
}

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.

TableView in iCarousel finding nil while unwrapping optional value

So, I have a carousel of "BillSplitters" and on each carousel item it should display the uniques items a BillSplitter is having. So I'm getting fatal error: unexpectedly found nil while unwrapping an Optional value Normally i can slowly hone in on an error like this i find the issue but when following on from a breakpoint line by line it enters into the iCarousel code which i cant follow. Im also sure theres nothing going wrong in i carousel as if i dont addSubview(tableView) then it runs fine. It also seems to create the first couple of tableviews and add them fine and then gets the error. Here is the code im using:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let splitter = allBillSplitters[carouselIndex]
if (splitter.items?.count)! > 0 {
return (splitter.items?.count)!
} else {
TableViewHelper.EmptyMessage("\(splitter.name!) has no items to pay for.\nGo back to assign some items to their name.", tableView: tableView)
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ItemCell = tableView.dequeueReusableCell(withIdentifier: "SplitterItemCell") as! ItemCell
let itemsSet = allBillSplitters[carouselIndex].items
let items = itemsSet?.allObjects as! [Item]
let item = items[indexPath.row]
let count = item.billSplitters?.count
if count! > 1 {
cell.name!.text = "\(item.name!) split \(count!) ways"
cell.price!.text = "£\(Double(item.price)/Double(count!))"
} else {
cell.name!.text = item.name!
cell.price!.text = "£\(item.price)"
}
return cell
}
func numberOfItems(in carousel: iCarousel) -> Int {
return allBillSplitters.count
}
I've read in a few places that I should remove the if let view = view statement in the following function as it's not re-using the items and always creating new ones. If I leave it in I get the same error immediately when creating the first carousel item and when I remove it, it happens on the creating the third i carousel item.
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
carouselIndex = index
var splitterView: UIImageView
var nameLabel: UILabel
var emailLabel: UILabel
var totalLabel: UILabel
var tableView: UITableView
let splitter = allBillSplitters[index]
//reuse view if available, otherwise create a new view
if let view = view as? UIImageView {
splitterView = view
//get a reference to the label in the recycled view
nameLabel = splitterView.viewWithTag(1) as! UILabel
emailLabel = splitterView.viewWithTag(2) as! UILabel
totalLabel = splitterView.viewWithTag(3) as! UILabel
tableView = splitterView.viewWithTag(4) as! UITableView
} else {
let height = carousel.contentView.frame.height - 85
let width = carousel.contentView.frame.width - 80
//don't do anything specific to the index within
//this `if ... else` statement because the view will be
//recycled and used with other index values later
splitterView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
splitterView.layer.cornerRadius = 10
splitterView.clipsToBounds = true
splitterView.image = UIImage(data: splitter.image as! Data, scale:1.0)
splitterView.contentMode = .scaleAspectFit
splitterView.backgroundColor = UIColor(netHex: 0xCA9875)
let viewWidth = Int(splitterView.frame.width)
nameLabel = UILabel(frame: CGRect(x: 5, y: 0, width: viewWidth, height: 30))
nameLabel.backgroundColor = .clear
nameLabel.backgroundColor?.withAlphaComponent(0.1)
nameLabel.textAlignment = .left
nameLabel.font = nameLabel.font.withSize(20)
nameLabel.tag = 1
emailLabel = UILabel(frame: CGRect(x: 5, y: 30, width: viewWidth, height: 15))
emailLabel.backgroundColor = .clear
emailLabel.textAlignment = .left
emailLabel.font = emailLabel.font.withSize(15)
emailLabel.tag = 2
totalLabel = UILabel(frame: CGRect(x: 5, y: 45, width: viewWidth, height: 15))
totalLabel.backgroundColor = .clear
totalLabel.textAlignment = .left
totalLabel.font = totalLabel.font.withSize(15)
totalLabel.tag = 3
let tableViewHeight = height - 65
let frame = CGRect(x: 0, y: 65, width: width, height: tableViewHeight)
tableView = UITableView(frame: frame)
tableView.delegate = self
tableView.dataSource = self
tableView.tag = 4
totalLabel.backgroundColor = .clear
splitterView.addSubview(nameLabel)
splitterView.addSubview(emailLabel)
splitterView.addSubview(totalLabel)
splitterView.addSubview(tableView)
}
//set item label
//remember to always set any properties of your carousel item
//views outside of the `if (view == nil) {...}` check otherwise
//you'll get weird issues with carousel item content appearing
//in the wrong place in the carousel
nameLabel.text = "\(allBillSplitters[index].name!)"
emailLabel.text = "\(allBillSplitters[index].email!)"
totalLabel.text = "£\(allBillSplitters[index].total)"
return splitterView
}
func carousel(_ carousel: iCarousel, valueFor option: iCarouselOption, withDefault value: CGFloat) -> CGFloat {
switch option {
case .spacing:
return value * 1.2
case .fadeMin:
return 0.0
case .fadeMinAlpha:
return 0.3
case .fadeMax:
return 0.0
default:
return value
}
}
I've looked all over and can't find a solution so any help would be great. Thanks
I'm an idiot. Forgot the following:
tableView.register(CarouselTableViewCell.classForCoder(), forCellReuseIdentifier: "carouselTableViewCell")
in tableviews cellForRowAt function