I'm passing data to a custom UITableViewCell and based on that data I want to show or hide a dynamically added subview. I'm reusing a cell which is created in the storyboard. Everything is working as expected, until some of the cells are reused, for example while scrolling it will "randomly" hide or show the dynamic added subview.
What I need
I need a way to set the data of a cell through a method (setData), adding a dynamically created subview, while allowing a cell to be reused without creating glitches in its appearance, in particular the added subview as well the cells state.
Problem
I don't know where I should create the subview, so it doesn't have to be recreated when the cell is reused and so it won't bug when I want to hide or show it in the setData method. As well having access to the IBOutlet storyboardLabel while creating the new subview.
CustomTableViewCell
class CustomTableViewCell : UITableViewCell {
var data: DataItem?
var customSubview: UIView?
#IBOutlet weak var storyboardLabel: UILabel!
//setting the data of a cell and adding the subview
func setData(DataItem data) {
// adding the view
let customSubview = UIView.init(...)
customSubview.bounds = storyboardLabel.bounds
customSubview.hidden = data.showSubview
self.contentView.addSubview(customSubview)
}
}
Adding the cell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("storyboardTableCell") as! CustomTableViewCell
cell.setData(self.data[indexPath.section][indexPath.row] as! DataItem)
return cell
}
Everything is working as expected, until some of the cells are reused...
Nothing is working as expected: you are adding object to a cached UITableViewCell, and of course, when you are passed that cached cell again on a subsequent cellForRowAtIndexPath, you are adding new objects.
Consider using a collection of views in IB, each satisfying your cell organization, thus saving adding subviews programmatically.
You need to clean up your cell.
Either remove everything you have added in your cellForRowAtIndexPath
You can use tags and viewWithTag if you want to refrain from re-adding already existing views
Or implement prepareForReuse()
You can find an example (and a similar discussion) on this Stack Overflow post.
Related
I have a table view with 3 types of custom cells.
Interface in storyboard.
3 different classes in the project for the cells.
I'm doing this currently and the cells IBOutlets are coming out to be nil.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = ListCell()
cell.configureCell(Data[indexPath.row])
return cell
}
class ListCell : UITableViewCell {
#IBOutlet weak var selectButton: UIButton!
func configureCell(data: ParamList) {
selectButton.setTitle("Select " + data.name, forState: .Normal)
}
#IBAction func selectButtonPressed(sender: AnyObject) {
print(selectButton.currentTitle)
}
}
I don't want to reuse any cells because every cell has different properties.
Some have textboxes other have different interactive matter which needs to be submitted in the end to a webservice.
I want to create new cells in memory for every cell.
Max cells will be around 15. So won't have much of a memory problems.
Any way I can Do that??
I did finally solve this.
Here's what I did for the same.
I am describing my process if anyone else wanna do the same.
Warning, only do this if absolutely necessary and you do not have that many cells that can affect the performance.
For making UITableView's cells to remain in memory even if scrolled outside the screen encapsulate your table view in a scroll view, and set your UITableView's scrollEnabled to false.
If using Storyboards set a height constraint to your TableView and make an IBOutlet to your code file.
Programatically add new cells with different identifiers.
"\(indexPath.section)\(indexPath.row)"
After getting your data, set your tableView's height to a multiplier of your data count.
i.e.
tableViewHeightConstraint.constant = heightOfOneCell * data.count
tableView.delegate = self
tableView.dataSource = self
This way you can scroll with the scrollView and all your cell will still be in the memory.
Set nil inside the method dequeueReusableCellWithIdentifier. So you can get what number of cells you want in memory.
I've done many apps with simple tableviews and I'm comfortable with them. I've never used a custom tableViewCell until today. I started with a really custom cell with two labels and that renders normally. I can populate my two labels with my data, no problem. I thought it would be equally easy to add the standard checkmark using:
cell.accessoryType = .Checkmark
My cells render the labels properly but I don't get a checkmark. Is there something extra I need to do to render an accessory type with a custom tableViewCell?
edit: I tried to not just use a default accessoryType. I created an image, an imageview, and set cell.accessoryView to that view and that doesn't display either. Obviously I'm missing something in my custom cell
try this code:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
// your cell coding here and set cell accessory view checkmark
cell.accessoryType = IUTableViewCellAccessoryType.Checkmark
}
UITableViewCell consist of 2 subviews:
contentView
accessoryView
When you are creating your own custom cell (that extends UITableViewCell) add your views to contentView. (See docs)
It is possible that you have added your views directly to UITableViewCell and in this case you are drawing your subviews on top of accessoryView.
I am creating a custom cell and I add subviews to it for text mostly. At first the subviews were not getting deleted when the cell was reused, so the text would overwrite and blot really bad. Then I added this for loop to the function and it solved my problem except that it also completely removes my imageView and it never gets re-added, so my whole tableview is missing the image that is supposed to be associated with each cell.
The if statement didn't work, but I didn't explicitly use cell.addSubView() as i did for the cell labels, so I am wondering if that has something to do with it. I have googled and found that most people are saying this worked for them, but I just cannot seem to figure it out.
Why does this remove my images, why won't they come pack like the other subviews, and how can I fix this?
I have the following code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
for subView in cell.subviews {
if subView as? NSObject != cell.imageView {
subView.removeFromSuperview()
}
}
var statusImage = UIImage(named:imageConfigration(indexPath.row))!
cell.imageView?.image = statusImage
// ...
}
Give your imageView a tag in storyboard or xib or programmatically what you are using. Now in the for loop of subviews check if tag of the subview is equal to tag to imageview dont remove it.
Also another option to solve this if it fits your requirement is to hide all the elements added to cell except imageview. That will be much faster then looping in subviews and removing it you never know what all subview are attached to cell other then you added by iOS but thats if it fits your req. Let me know in case of any concern
This how you can do this
cell.textLabel.hidden = YES;
I need get the indexPath.row of a components (switch, checks..) located inside my CustomCellS class. I'm trying code below in custom cell class, but I get an error:
class CustomCellS: UITableViewCell{
#IBOutlet weak var switch: UISwitch!
var cell: UITableViewCell = switch.superview as UITableViewCell //crash
var tableView: UITableView = cell.superview as UITableView
var indexPath: NSIndexPath = tableView.indexPathForCell(cell)
...
}
Error: Thread 1: EXC_Breakpoint(code=EXC_i386_BPT, subcode=0x0)
How can get the indexPath.row of this components?
Thanks!
This shouldn't build at all. switch is a keyword. If you really want to use this word as a variable name you should put it inside backticks.
It seems that all you're trying to do is to access the indexPath of the cell inside it. In that case you can have a property for it and set in when you create the cell in your tableView method.
But from a design point of view, indexPath is something that your tableView uses to keep track of things and your cell should usually be 'blind' to it because it's not its concern (and that's why UITableViewCell doesn't have such property built-in.) You'll probably be able to do what you need to do without exposing unnecessary information to your cell.
The first problem is that the switch's view is very likely not the UITableViewCell itself. UITableViewCell has a few subviews, one of which is its contentView. That contentView is likely the superview of your UISwitch. However, this is easily worked around; if you have a cell that contains the switch, then by definition you already have a reference to the cell itself.
The second problem is that the cell's superview is not guaranteed to be the tableView. If you really need a cell to have a reference to the tableview, then you should probably add the tableView as a weak property and pass a reference to the tableview when you create the cell.
I'm using a "Static Cells" table view created in my storyboard file. However I'd like to update the text on one of these cells when a setting is changed.
I'm pushing a view controller which updates the setting. I'm trying to use a callback to change the cell's text when it's popped off the stack, but by that point the cell has apparently been recycled or reused, so the old object is off screen and no longer used in the table view.
Is there a way I can update this text, and make it permanent (so that when the cell is scrolled off screen and comes back, the new value still appears)?
Assuming your table view hierarchy is along the lines of:
Table View (static cells)
- Table View Section
- Table View Cell
- UILabel (with the text property you want to modify)
Declare an IBOutlet UILabel in your code, and wire it up in the storyboard's UILabel in the table view hierarchy above.
In your callback method, set your UILabel's text property as you see fit.
You can store text that you want to change as a property and use it in:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)
switch (indexPath.section, indexPath.row) {
case (0, 3):
cell.textLabel?.text = yourProperty
default: break
}
return cell
}