I have a UITableView with a transparent background color and cells which also have a transparent background color. When I reload my tableView with:
dataSource = some new data
tableView.reloadData()
I can see the new cells overlap the old ones.
I did try to use use
tableView.beginUpdates()
// remove all rows here
change data source
// insert new rows here
tableView.endUpdates()
but it did not work. I tried as well tableView.reloadRowsAtIndexPath(...) but still no luck.
And finally I set all my cells and my table view to clear graphic context when redrawn but it did not manage to fix this issue.
Any help would be greatly appreciated.
My cell creation function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("suggestioncell")
cell.backgroundColor = UIColor.whiteColor().alpha(0.1)
cell.textLabel?.text = (suggestions![indexPath.row] as! SVPlacemark).formattedAddress
cell.clearsContextBeforeDrawing = true
cell.contentView.clearsContextBeforeDrawing = true
return cell
}
Try overriding prepareForReuse in you UITableViewCell subclass, and reset content there.
Here's what the documentation says about that:
Prepares a reusable cell for reuse by the table view's delegate.
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.
Custom UITableViewCell class:
class customCell: UITableViewCell {
override func prepareForReuse() {
self.textLabel?.text = nil
}
}
In your cellForRowAtIndexPath method:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("suggestionCell", forIndexPath: indexPath) as! customCell
cell.backgroundColor = UIColor.whiteColor().alpha(0.1)
cell.textLabel?.text = (suggestions![indexPath.row] as! SVPlacemark).formattedAddress
return cell
}
And, of course in your XIB/Storyboard, set the cell class to CustomCell, and set its reuse identifier.
Related
I would like to confirm the approach I took to solve an issue with dequeuing custom cells in a UITableView as it scrolls such that the cells do not contain the old cell's data...
The app that contains a UITableView with custom UITableViewCells ("CustomCell"). Each CustomCell contains a UIStackView with one or more custom views via a nib ("CustomView"). I reuse the CustomCell as follows:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(ReuseIdentifierCustomCell, forIndexPath: indexPath) as! CustomCell
configureCell(cell, atIndexPath: indexPath)
return cell
}
The issue was that the cell would contain "old" data as the cell was being reused. To fix this, I override the prepareForReuse method in CustomCell as follows:
override func prepareForReuse() {
super.prepareForReuse()
for case let view as CustomView in stackView.subviews {
view.removeFromSuperview()
}
}
Then in layoutSubviews, I add the subviews back in:
override func layoutSubviews() {
super.layoutSubviews()
if stackView.subviews.isEmpty {
addCustomViewsToCell()
}
}
Performance seems fine thus far, but curious if this is a proper approach or if I will run into issues with scale in the future. I have not been able to find another workable approach thus far.
Thanks
Your code to reuse cells is correct. A common approach is to configure your cell's data within the cellForRowAtIndexPath function by setting a variable or calling a function on your custom cell:
let cell = tableView.dequeueReusableCellWithIdentifier(ReuseIdentifierCustomCell, forIndexPath: indexPath) as! CustomCell
cell.data = myData[indexPath.row] // where myData is an array of type [Data]
return cell
Your cell would be in charge of its own layout to display the new data:
var data: Data {
didSet {
// configure and refresh your UI here
}
}
I suspect your issue has to do with your configureCell function. If you can, move this code into your cell's logic instead. This will be cleaner and easier to understand.
As far as performance, you might be fine now if your stack views don't have much content in them, but if they continue to grow in complexity you might see frame rate drops on older devices.
First, I wanted to point out the reason I want to make a common call, when a reusable cell is dequeued in a base class, it's because of this line of code you will see again soon further in my question:
cell.backgroundColor = cell.contentView.backgroundColor;
This is a bug fix for iPad not respecting the UITableView.backgroundColor = myCustomColor and UITableViewCell.backgroundColor = clearColor I have set. iPad displays white instead, everything is fine in iPhone versions, you can see this bug here, I have to set the background color again each time the cell is dequeued that is the only solution that works for me. I am trying to do this once in my base class, and come up with a solution where I do not have to remember to call a func for every child class (might not be possible).
I have a couple custom UITableViewControllers classes, let's call them ATableViewController and BTableViewController they inherit from a base class called UIBaseDashboardTableViewController which inherits from UITableViewController.
I am generating dynamic Prototype Table cells and making use of the function below in ATableViewController and BTableViewController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ATableViewCellId", forIndexPath: indexPath)
//common setting to fix always white background in iPad bug
cell.backgroundColor = cell.contentView.backgroundColor;
return cell
}
The TableViewCell Id ACustomTableCellId is unique or different for ATableViewController and BTableViewController. I have a common setting for all my UITableViewControllers that inherit from my base class, UIBaseDashboardTableViewController. You can see the backgroundColor line of code above is my common setting that will be the same in all child classes of UIBaseDashboardTableViewController. In each child class I first tried to do the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
super.tableView(tableView: tableView, cellForRowAtIndexPath: indexPath)
...
}
But that is not going to work, I need the ReusableCellIndentifer.
My current solution, which really is just fine probably, is the following, in my child classes I have the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let data = dataArray[indexPath.row]
let dequeuedCell = tableView.dequeueReusableCellWithIdentifier("BTableViewCellID", forIndexPath: indexPath)
let cell = dequeuedCell as! MyCustomTableViewCell
// Configure the cell...
cell.model = data
//call common settings for cells function in base class
super.setDequeueReusableCellCommon(cell)
return cell
}
And then in my base class UIBaseDashboardTableViewController I implemented:
func setDequeueReusableCellCommon(cell: UITableViewCell) {
cell.backgroundColor = cell.contentView.backgroundColor
}
The only downside to this is that I have to remember to call super.v setDequeueReusableCellCommon in all my child classes.
Any better suggestions on how solve this?
You are changing background color for cell, but made inheritance for tableViews. Use inheritance for tableViewCell, not the whole tableView. And in root class for tableViewCell setup self.backgroundColor = self.contentView.backgroundColor in awakeFromNib method.
I am trying to get a custom tableView cell with a textView inside working in my tableView. I have made a custom UITableViewCell with a textView inside it.
I can get the custom made UITableViewCell with the textView inside to appear in the UITableView.
I can click inside the textView to type something, but when I finish typing and click on another tableViewCell, the first tableViewCell with the textView inside disappears. After disappearing, it becomes an empty tableViewCell. XCode gives this message:
"no index path for table cell being reused"
However, when I scroll away in the tableView and scroll back to the empty tableViewCell, it reappears.
I don't know how to keep the tableViewCell from disappearing. It seems like the answer has something to do with using the restorationIdentifier inside of UITableView, but I'm not sure how to use it. In the docs, it says to use restorationIdentifier for state preservation.
Here is the relevant code I have:
inside ViewDidLoad():
tableView.registerClass(PhotoAndRateTableViewCell.classForCoder(), forCellReuseIdentifier: ReuseIds.reviewCell)
tableView.registerNib(UINib(nibName: "PhotoAndRateTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: ReuseIds.reviewCell)
inside cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var section = indexPath.section
let cell = UITableViewCell()
var cell = tableView.dequeueReusableCellWithIdentifier(ReuseIds.reviewCell, forIndexPath: indexPath) as PhotoAndRateTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
You're misusing the dequeue procedure:
var cell = tableView.dequeueReusableCellWithIdentifier(ReuseIds.reviewCell, forIndexPath: indexPath) as? PhotoAndRateTableViewCell
if (cell == nil) {
cell = PhotoAndRateTableviewCell();
}
You want to reuse a cell if available, or create a new one if not. In your case, you're creating a cell every time (of the generic class) and then attempting to dequeue a cell from your custom class (which has never been created)
As far as preserving the data, you need to implement the prepareForReuse method in the table cell which should clear whatever index specific data was contained in the cell. Then in cellForRow you can re-set the data for the cell for re-appearance
Having a UITableViewControllerand using:
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as! UITableViewCell
return cell
}
Using dequeueReusableCellWithIdentifier:forIndexPath:'' instead ofdequeueReusableCellWithIdentifier`` how can I determine if a cell is a new one or a reused one?
Create your own custom cell class to use for the reuse identifier. Then, inside that cell, implement awakeFromNib and make your changes to the cell. This will only be called once when your cell is loaded from its NIB file.
Any other changes that need to be made when the cell is reused can be made in prepareForReuse.
I'm working in Swift with one TableViewController with one prototype cell. The cell has a reuse identifier specified in the storyboard, but it never dequeues properly. I always get the "unexpectedly found nil while unwrapping an Optional value" error.
I've properly registered the class as follows:
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "myNewCell")
The offending code is:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("myNewCell") as UITableViewCell
let textField = cell.viewWithTag(123) as UITextField
textField.text = "test"
return cell
}
I feel like I've tried everything here but it never properly gives a cell with that identifier. Even using the fallback (if nil, create a cell with that identifier) still gives the error. It's definitely having trouble getting a cell with that identifier, but it's registered and specified in the storyboard... Any assistance is greatly appreciated!
When using cell prototypes, you do not call registerClass. The storyboard does that for you. If the cell prototype has its identifier specified, then just all dequeueReusableCellWithIdentifier and it should find your cell prototype without incident.
I'd suggest checking the spelling/capitalization of the identifier in storyboard and make sure it is identical to what is used in cellForRowAtIndexPath code.
I notice that you are trying to access a cell's label using a tag number. Nowadays, when dealing with custom cell layouts, we'd generally create our own table view subclass, e.g.:
// CustomTableViewCell.swift
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var customTextField: UITextField! // note, I would use something other than textLabel to avoid confusion with base class
}
We'd then go to our cell prototype and specify its base class:
We'd also set the cell prototype's identifier:
We'd then hook up the outlet between the cell prototype and our custom class #IBOutlet.
Having done all of that, the cellForRowAtIndexPath would be:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myNewCell", forIndexPath: indexPath) as CustomTableViewCell
cell.customTextField.text = "Row \(indexPath.row)"
return cell
}
If dequeueReusableCellWithIdentifier is giving you problems then just don't use it.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = UITableViewCell()
let textField = cell.viewWithTag(123) as UITextField
textField.text = "test"
return cell
}