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
Related
I'm using the typical method of dynamically sizing table view cells that contain text views in them:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
This works just fine for the cells I actually instantiate and use, but it has a strange side effect: the empty cells that the table view shows below instantiated cells end up resizing their height alongside whichever custom cell I'm currently editing.
So when I input three, four, or more lines of text into a newly added custom cell...
...all the other prototype-based custom cells already filled in the
table remain unchanged
...the custom cell being edited resizes dynamically as intended
...but all the "ghost" cells below the last custom cell in the table
view end up dynamically expanding alongside the custom cell that's
being edited
My first thought was that it must have something to do with how I dequeue cells, and specifically what I return when dequeueing doesn't happen:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let myCell = tableView.dequeueReusableCell(withIdentifier: "myCustomCell", for: indexPath) as? CustomCell {
return myCell
} else {
return CustomCell()
}
}
But I tried returning both my custom cell and just UITableViewCell() in the else block and the result is the same.
Does anyone know why this could possibly be happening and/or how to get around it?
In my tableview, every cell will be different and determined by a JSON response from server. And there will be infinite possibilities. So defining a prototype for each type of cell is not possible.
For example, one cell will have labels and buttons, another cell have images and buttons in different orders.
How to achieve this dynamic structure in tableview cells?
Currently what I am doing is: adding views as subview in cellForRowAtIndexPath but scrolling is very laggy this way.
How to achieve this without affecting performance this much
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! MyCell
for myview in data[indexPath.row].myviews{
cell.addSubview(myview)
}
return cell
}
If you're using a table view then your content is going to scroll vertically, right?
There is a physical limit to the amount of UI that you can put horizontally. Limited by the screen size.
So I'm guessing your UI parts are being laid out vertically in the cell?
So instead of laying out a button, label, image, another button, and a text field vertically in a cell...
Create a cell type called ButtonCell, LabelCell, ImageCell, MultiLineLabelCell, TextFieldCell, etc...
So now, instead of creating one cell with all these elements added. You instead create multiple cells each containing one type of UI. Now you can dequeue your cells in any particular order (driven by your JSON) and won't lose the performance.
The only solution I see is to have empty cell and add/remove subviews as needed. But you should add new subviews to a cell only if you did not add them before.
For example:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! MyCell
if cell.contentView.viewWithTag(1) == nil {
let label = UILabel()
label.tag = 1
cell.contentView.addSubview(label)
}
let label = cell.contentView.viewWithTag(1)
// label config there
return cell
}
Also don't forget to add subviews to cell's contentView not to cell itself.
I have a static table with one static section. Other sections are dynamic.
I create Table Section and Table Cell for dynamic section. Set identifier for Cell, set custom class for it and even do:
self.tableView.registerClass(UncheckedStoreTableViewCell.self, forCellReuseIdentifier: "StoreCell")
if i don't register it with code, then i get:
'unable to dequeue a cell with identifier StoreCell - must register a
nib or a class for the identifier or connect a prototype cell in a
storyboard'
So when i use this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
return super.tableView(tableView, cellForRowAtIndexPath: indexPath)
}
let cell = tableView.dequeueReusableCellWithIdentifier("StoreCell", forIndexPath: indexPath) as! UncheckedStoreTableViewCell
return cell
}
It works. But if i'm trying to change label: cell.myLabel.text = "one"
or just print(cell.myLabel) got
BAD_INSTRUCTION
You can definitely use dynamic cells in a static table view.
Don't expect a static table view to register your cell's identifier for you. Just do it yourself.
Do you have outlets in the cell class to some view in interface builder? If I were you I wouldn't expect the table view to know about that. It will instantiate your cell class, and that's it. No outlets will be set. I think this is related: load nib in view subclass
By the way, if you've defined a custom .nib for your cell, there's this method: registerNib(_:forCellReuseIdentifier:)
You do not need to register your cell in code.
You have correctly set the identifier of the cell, however this is not enough. In addition to this you also need to open Identity Inspector for your cell and set the class of the cell to be UncheckedStoreTableViewCell. Here is an image showing you where you should set it:
Without this step Xcode will not be able to correctly associate your cell identifier with your custom cell as it doesn't know anything about it!
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.
Recently I noticed the following behavior and I can't understand why does it work as it is.
I have a UITableView on my storyboard and the following code in the view controller:
override func viewDidLoad() {
super.viewDidLoad()
self.threadsTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell? = self.threadsTableView.dequeueReusableCellWithIdentifier("cell") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "cell")
}
cell!.detailTextLabel!.text = "str"
}
In this case application crashes at the
cell!.detailTextLabel!.text = "str"
line because detailTextLabel is nil
Then I decided to remove the registerClass function call and noticed that code doesn't really crash anymore, but dequeueReusableCellWithIdentifier function call returns nil every time. I think that I don't somehow enable the "Subtitle" style of the table view's cells in the storyboard, but I don't see any option like this in the Interface Builder.
What am I doing wrong? How can I fix it?
Thanks in advance.
In your storyboard, do you have prototype cells within your tableView? If not, drag a table view cell in from the object library (bottom right) and embed it in the table view. You can then use the attributes inspector for that cell to set the reuseIdentifier. If the cell style defaults to "Custom", amend it to "Subtitle" - IB will then automatically add labels for textLabel and detailTextLabel (and will hook them up to the outlets in UITableViewCell). See below: