Multiple cells got affected after calling cellForRowAtIndexPath - ios

I have a UITableView, and I want to change text color of the selected row. But I see every other 15 row got affected along with the one I clicked. This is tested under the master-detail sample project.
if let indexPath = self.tableView.indexPathForSelectedRow(){
self.tableView.cellForRowAtIndexPath(indexPath)?.textLabel?.textColor = UIColor.redColor()
}
I checked cellForRowAtIndexPath value in debug session, it seems returning only one object, how come other cells got affected too?

Cells are reused - almost certainly you are not resetting your cells to a base state in prepareForReuse and configuring them correctly on each call to cellForRowAtIndexPath.
When you look at a table view that can display some number of cells at once, typically there will only exist one more cell than can be shown. When a cell is moved off the screen it is placed in a pool of cells for reuse. Just before a cell moves onto the screen it is configured by you, in cellForRowAtIndexPath. If you have configured something in the cell and you do not configure that explicitly every time you return a cell from cellForRowAtIndexPath then that configuration persists in the cell that is in the reuse pool. The function prepareForReuse is also called before each cell is reused - if you have subclassed UITableViewCell then you can implement this function to return the cell to a base configuration, so that settings like text color do not unexpectedly affect multiple cells.
This approach makes it possible to scroll through an entire table view smoothly with a minimum amount of memory used. It is an expensive operation to create and destroy cells every time one disappears and a new one is required.
The simplest fix is to always set the text color in cellForRowAtIndexPath - either to the base color or to the special color you want in some cells.

Related

Cell reuse bug in collectionView

Hello to all dear friends. I'm experiencing a problem with cell reuse. In practice I select multiple cells. In the "didSelected" method, I modify a property, called "isSelectedCell", to true and add a green border (to indicate the selection); While in the "didDeselect" method I carry it to false and remove the color of the border. But when I scroll down some cells, never selected, appear to be selected and the property is true. Because? How do I prevent this. It seems that when a cell is reused, the properties take on old ones and not their own.
If you are using a custom cell, override prepareForReuse and reset all properties to default values
override func prepareForReuse() {
super.prepareForReuse()
// reset custom properties to default values
}
Cells are for reuse means they are only fixed number of views which are used again. You have to update cells for every item to be displayed in that cell in collectionView cellForItemAtIndexPath method. You've to put it inside data source like array of objects to return correct value for every item. Hence it will remember to display which property on item.
Reusable cells are necesary to not bloat up the device memory and reuse same views.
it's due to the cell reuse of CollectionView.
as you can see in this article
when a cell disappear, it's reused to be the new one that going to appear.

Custom table cell without dequeueReusableCellWithIdentifier in Swift

I have an averagely sized table which is working perfectly, except for the row insertion animation (withRowAnimation). I've overriden it (with the help of stack community) to have a longer duration than the original system animation and it works just fine, but ...
Because i'm using custom cells as reusable cells - each time i scroll this effect is getting wiped out.
So the only solution i see is to stop the reuse.
I know this will interfere with the memory, but in this case its the only scenario left (of which i know).
My general question is how do i load a custom cell nib without using dequeueReusableCellWithIdentifier so that the reuse wouldn't happen.
Thank you.
You should use dequeueReusableCellWithIdentifier, not using it is a recipe for disaster.
Take a look into your code and when you dequeue a cell (reused or not, most of the time it'll be reused), the first thing you should do is set all elements in that cell back to the original state, so that that reused cell behaves like a non reused cell.
I don't know what you animation/implementation is about but here an example from a project I wrote with custom cells :
In each cell I added buttons, this could be 1, 2, 3,... buttons. If I did nothing in a reused cell I would have more buttons than expected because the old buttons would still be there...
So in my code the first thing I did after dequeing the cell would be to remove all the buttons.
let cell = tableView.dequeueReusableCellWithIdentifier("RelatedCell") as! RelatedCell
cell.removeAllActionButtons()
where my removeAllActionButtons (a method in my custom cell) method would be something like:
for button in actionButtons {
button.removeFromSuperview()
}
actionButtons is an Array containing all that buttons added to that custom cell

How can I make the uicollectionview not dequeue the cells?

I don't want cells to have to be generated when they come on screen for the first time. I also don't want cells that have gone off screen to have to be regenerated when they come back on the screen.
How can I have all the cells generated on loading the UICollectionView and stay that way as I scroll up and down?
You could manage your own list of cells (pre-create them based on the datasource, create any new ones as the datasource updates, and return the appropriate cell for the indexPath desired instead of dequeuing a cell inside cellForItemAtIndexPath) but as a general rule, that's a bad idea. You may have some specific case where cell configuration performance is poor, but the answer is usually "improve cell configuration" and rarely "keep everything in memory".
Speculating: If you're thinking of doing this in order to preserve state or cache some information in the cell, that's the wrong place to put it. The cell is presentation; keep that info in the datasource element so that whatever needs to be represented can be applied to whatever cell is being used right now.

Can you store state in a custom UICollectionViewCell? Or can the state get erased (like when the cell scrolls off the screen)?

If I create a UICollectionViewCell subclass like:
class StudentCell: UICollectionViewCell {
var student: Student?
}
And in my controller I implement UICollectionView's didSelectItemAtIndexPath and set the variable:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if let studentCell = collectionView.cellForItemAtIndexPath(indexPath) as? StudentCell {
studentCell.student = self.someStudent
}
}
When I click on the cell it should set student, but if the cell is scrolled off screen it seems like the whole cell might get wiped and when it comes back on screen it would need to rebuild itself in cellForItemAtIndexPath.
The problem I have is that I have a UICollectionView with cells that do not scroll off the screen that I'm storing data in. Because they don't leave the screen I assume they should not get wiped, but one of the cells does not seem to keep it's variable, which makes me think maybe it's getting wiped and I may need to move the state outside of the cells to a dictionary or something and in didSelectItemAtIndexPath instead of setting the variable in the cell I'd set the dictionary. Whenever I need to access the data instead of asking the cell for it I'd look it up in the dictionary.
But either way, I was wondering if it's possible (or a bad idea) to set it in the cell or not.
Yes, cells in both UICollectionView and UITableView can (will) be reused at the systems discretion and should not be used to store state information, only display information. Specifically, both views will reuse cells when they are scrolled off-screen, but there's no guarantee this is the only time they'll be reused. The usual way to handle this is to define some kind of cell data object which stores the data for each cell (visible and not) and refresh the cell view from that as needed/requested.
Tables display their data in cells. A cell is related to a row but it’s not exactly the same. A cell is a view that shows a row of data that happens to be visible at that moment. If your table can show 10 rows at a time on the screen, then it only has10 cells, even though there may be hundreds of rows with actual data. Whenever a row scrolls off the screen and becomes invisible, its cell will be re-used for a new row that scrolls into the screen.

Force UITableView to cache reusable cells

I use a table view with relatively 'heavy' custom table view cells - say there's is around 100 subviews in a single cell (full hierarchy). Despite of that I've been able to reach very smooth scrolling - using standart practices like working only with opaque views, hiding unused subviews (or even removing from the view hierarchy), pre-loading cells height etc.
The problem is that it's still relatively costly to create a single cell instance. Because of how the standard UITableView's cell reusage logic works this problem is only noticeable in specific conditions.
Usually when table view is loaded (and intitial content is displayed) it creates almost all required cells instances - so when you start scrolling it rarely needs to create any more reusable cells. But if for some reason it only displays a few cells at the beginning it would obviously need to create more when scrolling is initiated. In my case this is noticeable if there's a wide table view header or if first cells are big enough. When that happens once you start scrolling there's a noticeable lag for a few moments which is clearly caused by new cell instances being created. Nothing really ugly but still noticeable especially compared to absolutely smooth scrolling after that.
Now the obvious solution for that would be to make cells 'lighter' - like break them into different more specific types so each can contain less subviews. But because of the way my content is organized that would be a very complex solution.
So my question is - is there any good way to 'trick' table view logic to force loading and caching of specific number of reusable cell instances right away - despite of the number of cells actually visible?
One of the options I've been thinking of is to have own explicit cache of cells and pre-populate it with cells when needed (say in -viewDidLoad). The problem I have with this approach is that you have to known exact type of cells you will need beforehand - doesn't work well for me. The improvement would be to build cache when the initial data is loaded (so at least the exact type of content is known) but I've been wondering if there are any simple options.
You can do it by adding your own "secondary cache" layer to your data source.
When you initialize your data source, pre-create as many cells as needed, and put them into an array. When your dequeueReusableCellWithIdentifier fails, go for that array first, and take a cell from there. Once the array is exhausted, start creating new cells (if you create enough cells upfront, you shouldn't need to create new ones on the fly: dequeuing will eventually stop failing when you have enough cells rotating in and out of visibility).
I too had a similar situation wherein I wanted to load the several websites in different cells and loadrequest must be triggered only once. Idea is to avoid duplicate calls when the cell is made visible/invisible by scrolling up or down. say I have 5 cells in my tableview and each cell has a webview which loads the different websites. loadrequest will be called once and cell will be cached. here goes the simplest implementation.
Variable declaration
let weblinks:[String] = ["http://www.google.com",
"http://www.facebook.com",
"http://www.yahoo.com",
"http://www.puthiyathalaimurai.com/",
"http://www.github.com"]
var mycells:[MyTableViewCell] = [MyTableViewCell(),
MyTableViewCell(),
MyTableViewCell(),
MyTableViewCell(),
MyTableViewCell()]
CellForRow. I hope you know what it does,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = String(describing: MyTableViewCell.self)
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? MyTableViewCell
if cell?.isNewCell == true { //return from cache
return mycells[indexPath.row]
}
cell?.title.text = weblinks[indexPath.row].uppercased()
cell?.web.loadRequest(URLRequest(url: URL(string: weblinks[indexPath.row])!))
cell?.isNewCell = true //mark it for cache
mycells[indexPath.row] = cell!
return cell!
}

Resources