I have a custom UITableViewCell that has data that I get from the server. I set the reuseIdentifier of the cell when the data comes in. Everything works fine, until I do a pull down to refresh and get new/updated data. The identifier from the server is the same, but the data may be different (which is an expected result in this case). When this happens I need to re-create the cells, and keep the same reuseIdentifier. I know that not setting the reuseIdentifier is one way around this, but that's a very bad idea, that hurts performance.
I've looked at plenty of question here involving reuseIdentifier's, but none of them seam to answer my question.
Thank you for any insight you have!
There's no need to clear the reuseIdentifier. Simply reload the table view after setting up your new data. All visible cells will be reloaded. Using the same reuseIdentifier is fine. As long as your cellForRowAtIndexPath method is using the new data to populate each cell, you will get the desired results.
Update - The comment by mkral is a good clarification. The reuseIdentifier represents the type of cell, not the data. So the reuseIdentifier should have nothing to do at all with the identifier from the server unless the server's identifier affects the type of cells being shown.
Related
I am wondering what exactly happens, when you dont use dequeueReusableCellWithIdentifier in the cellForRowAtIndexPath-method.
In one Project I am collaborating we have different types of custom UITableViewCells which all appear in one single tableview. So here we fill arrays with all TableCells that should be displayed. These arrays are not very big (10-15 Cells) so for us that way works even not using any identifiers for dequeueReusableCellWithIdentifier. The next question is how at all you could use identifiers resp. dequeueReusableCellWithIdentifier when using different Cells in one single section of an UITableView. Is someone here hwo can explain, what exactly happens in background? Regards Nils
The dequeueReusableCellWithIdentifier is something that reminds me of Flyweight pattern.
Since allocation and instantiation of a cell can be an expensive task, using this mechanism you have the opportunity to create only the first visible cells and later reuse them just changing their contents. Scrolling animation must be as fast as possible to give a good experience to the user.
Is it worth it? Yes and it basically comes for free, we just need to pay attention that some old data can be still present in a new visualization, the trick is to always implement the method -prepareForReuse() correctly, here, you can eventually wipe all displayed data before setting the new one.
If you want to use different cells in the same section is absolutely possible, also if they have different height. You just need to crate different cell identifiers, one for each cells and tie them somehow along with your data.
I usually map data to be displayed in struct (swift) or dictionaries along with a key for the cell identifier to be used.
If your type of cells are representing themselves while scrolling you should dequeue them.
All,
I hope most of you know that with ios7 there is not need to do a null check for tableview reuse
if (cell == nil) {
But unfortunately, because of that the cells are always reinitialized, as we put the code in the same method for initializing values. The problem is only with text fields inside the tableview though.
Let me explain the scenario. I have a table view with multiple rows, and some rows contain multiple text boxes. I populate the textboxes with data from server when the page is loaded. Since the cells are always re-initialized as i explained above, whatever I enter in the field goes away and the server data is re populated once i scroll down and come back to the initial stage. This is because the populating the data code is also in the same place. After fetching a reusable cell it populates the data.
Previously till ios6, we used if(cell==nil) and hence we loaded server data inside the cell and when reusing the cell, this piece of code will never be called.
I have other dirty solutions, but would like to know if someone else has a graceful way of dealing this. Please help.
You just don't store any data in the table view cell but in the model that fills the table cell. This is always the way it should be done.
Looking from the MVC standpoint than the UITableViewCell is a view. Since it is reused by iOS you should use a model to the view.
Yes, this is the expected behavior of UITableView. For performance reasons, cells are reused. Thus, it is your responsibility to populate the views in a Table View Cell every time tableView:cellForRowAtIndexPath: is called.
The thing I don't understand from your question - are you making a network call every single time a cell comes into view? If so, cache the results somewhere. Or, if it's a small amount of data, consider just doing it all in one shot at the beginning (still need to be asynchronous though).
One thing I see a lot of developers do is move a lot of code into UITableViewCell subclasses, which sounds like a good idea because it's modular, but makes solutions for problems like this more difficult. Have the Table View Data Source manage the network calls.
If you need some inspiration, look at Apple's LazyTableImages sample.
I want to load a tableViewCell from the storyboard without using dequeueReusableCellWithIdentifier to create a prototype cell to reference before cellForRowAtIndex is called. Calling that outside of CellForRowAtIndex does funny stuff.
dequeueReusableCellWithIdentifier is still being used in cellForRowAtIndex as it normally has been.
I dynamically set the cell heights based on it's contents. This requires me to know where the size, positions, text attributes like font sizes, alignments, etc of views in the tableViewCell. Otherwise I have to hard code these values to match what's in the storyboard.
What I'm currently doing is create a new xib file with just the cell, load it from viewDidLoad, and keep a pointer to it.
-(void)viewDidLoad {
// typical coding stuff goes here
// load nib
UINib *nib = [UINib nibWithNibName:#"ContentTextCell" bundle:nil];
// assign nib to identifier
[self.tableView registerNib:nib forCellReuseIdentifier:#"ContentTextCell"];
// reference cell
NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
_referenceContentTextCell = [topLevelObjects objectAtIndex:0];
}
Is there any way for me to load the tableview cell without make it's own nib? Using dequeueReusableCellWithIdentifier:forIndexPath: causes the tableView to behave in an incorrect way.
Additional notes
I think a lot of people are under the impression I call the code on cellForRowAtIndex. It's called in viewDidLoad. It was always displayed as such but perhaps it was easy to understand when skimming the question. I also still use dequeueResuableCell as normal in cellForRowAtIndex. Just wanted to make that clear.
The row heights are dynamic. If you made the suggestion that I should make the row height 44 then you may want to read the question more carefully before attempting to answer the question.
The text I'm using is from a json file, it requires the paragraph, font and positioning of a textView to calculate the text height, which impacts the row height. I'd like to pull these pieces of data from the prototype cell rather then hard coding the values, and making sure they match what's in the storyboard.
The code already works as is. It runs fine. I just think it'd be more convenient to be able to pull the prototype cell from the storyboard rather than making a new xib for it.
If I understand correctly, the row height is a function of your model and some attributes of subviews in the cell, for example, a string from your model and the font size in the cell's text view.
I agree that keeping the view attributes of the cell's subviews in code seems redundant with keeping them in the storyboard, but you're also right that keeping a reference cell around is weird. It's weird whether you get the cell from a nib or whether you find a way to get it from the storyboard (which I don't think you can, which is the answer to your stated question).
Dequeueing a cell in heightForRowAtIndexPath makes little more sense and probably causes an infinite recursive loop as I'm sure you've found.
So you probably won't love this answer, but I think best idea is "redundancy with an attitude change". "Redundancy" means that you keep the view attributes in code, like a method that returns the textView's desired font pointSize. "Attitude change", means you don't think of this as redundancy. Instead, your code should be the authority on all of the view attributes pertinent to row height. Think of the prototype cells in the storyboard as just a way of visualizing what the correct coded values should be. I'd even recommend setting the attributes in code using the coded values when you configure the cell, especially if there aren't too many of them.
Finally, if your rowHeight calculation is elaborate, and it sounds like it is, be sure to also implement
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
and do a much quicker calculation therein (like return a constant).
I've just been working through an almost identical problem. You want the height, but the height depends on a bunch of the cell UI setup. It might even be a fairly simple task of checking the padding height above and below an image. Something which could get tweaked in IB several times before you are done so you don't want to duplicate the padding value in code, you want to check them against the current values in IB.
You can call dequeueReusableCellWithIdentifier: from anywhere (assuming you have a reference to the UITableView). So you don't need another way to get the cell. This will not cause recursion or infinite loop problems.
There is a catch though. The cell won't go back to the auto-reuse pool. A UITableView figures out which cells scroll off the screen and puts them in the pool, but this cell never goes onto the screen and so the table view never remove it from the screen and put it back in the pool. To make matters worse, the table still has a strong reference to it; so if you lose your reference, it's not going to get cleaned up, it's essentially leaked memory. You can't use it, the table view can't use it and it will stay in memory until the backing UITableView gets deallocated.
I found a few ways around problem:
Dequeue the cell once, store it someplace safe and use the same one every time you need to test out some cell formatting. You can even call prepareForReuse: to clean it up every time you want to test something new. Holding a single extra cell in memory is unlikely to be a killer for most apps.
Sneak it back into the re-use pool. Wait, what? Yeah, I said you can't do it, but you can, but the thought makes me cringe a little. Next time you get to cellForRowAtIndexPath: don't dequeue a new cell, use the one you've got laying around and return that one instead. This will slip your wayward cell back into the fold and it will eventually make it's way back into the reuse pool.
So you can do this.... should you? Well, that's a different question. Apple has provided some alternatives to try and get around this issue, but in some cases they just don't work effectively.
That's a bad pattern and you will start to get undesired behaviours which you have already seen some of. It might be better to store the height against the cell as a class method.
+ (CGFloat)requiredHeight;
{
return 44.f;
}
and call it as such
self.tableView.rowHeight = [OKACell requiredHeight];
This does mean you need to manage the height in two places but because of the descriptive name and class method it shouldn't be difficult to change when that time comes.
I think I'm not lucid anymore...
I want to display data - retrieved from a server - in a UItableView. To do so I created
A wrapper class to store data elements collected from a server. All of them are then stored into an array.
A UITableViewCell subclass to display the data into a custom cell
A Xib file, associated to the UITableViewCell defined in point 2)
In cellForRowAtIndexPath: , I dequeue instances of 2+3) an associate the fields of 1) to their counter parts in defined in 2).
To me this looks like a lot of stuff.
But removing 1) seems difficult, as dequeueReusableCellWithIdentifier: manages it's own pool of objects, so it doesn't really make sense to create an array of UITableViewCells in step 1)
Removing 3) may be possible, but adds extra over-head when setting up the cell in cellForRowAtIndexPath:
Anyone has found a way to trim-down on this class crowd ?
If your cell is not too complex, then you can create it by code in the controller you are displaying data.
If your cell has a higher complexity, then there isn't really a way to trim it down. Besides 2 classes + a resource file is not that much really for what you are trying to accomplish.
Apple's documentation says of the indexPath parameter:
The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the table view.
But register(Class|Nib):forCellReuseIdentifier: only specifies the reuse identifier to use, not the section or a set of index paths.
I thought perhaps UITableViewCell had some way of getting its hands on the index path, so it could, say, round its corners if in the first row of a section, but I'm not seeing it. At creation time, all it gets is its style and reuse identifier (initWithStyle:reuseIdentifier:); at reuse time, all it gets told is prepareForReuse.
Seeing as the old dequeueReusableCellWithIdentifier: is still supported, what sort of index-path-based configuration could it possibly be doing, if it can't rely on having the chance to do it, anyway?
I checked the Table View Programming Guide, but it hasn't been updated since iOS 5.
The most important difference between dequeueReusableCellWithIdentifier: and dequeueReusableCellWithIdentifier:indexPath: is that they are different methods! Thus they can behave differently, and they do. This has nothing to do with the indexPath, really; we just need a way to distinguish them.
The New Way
In particular, if you call dequeueReusableCellWithIdentifier:indexPath:, this is a sign that you are using the new iOS 6 register-and-dequeue system. So, if you have failed to register this identifier, you'll get a nice crash and a log message explaining the problem. This method will never return nil; it always returns a cell, either by creating a new one or by reusing one.
The Old Way
On the other hand, plain and simple dequeueReusableCellWithIdentifier: is old and has to be backward compatible. If you haven't registered this identifier, it won't complain: it will just return nil, leaving you high and dry. You'll have to create the cell yourself, as in the bad old days.
EDIT: But see also the answer by #svena! The new way (with indexPath:) has a second advantage I didn't know about: the cell is correctly sized at the time it is returned to you.
According to WWDC 2012 Session 200 - What's New In Cocoa Touch,
If you use - dequeueReusableCellWithIdentifier:forIndexPath: to dequeue your cell, it will be the right size and you'll be able to do layout inside your cell's contentView.
That's pretty much a quote from Chris Parker, a UIKit Engineer.
Up until iOS 6, you had to subclass your UITableViewCell and override - layoutSubviews if you wanted to make layout adjustments. From encapsulation point of view, this might still be the better solution – however, sometimes you just need a tiny adjustment, and now you can do that in - tableView:cellForRowAtIndexPath: instead.
I believe it is used to call the tableView:heightForRowAtIndexPath: method, if one exists, allowing the cell to be correctly sized.
I always thought that UIKit would round the corners of the top and bottom cells in a grouped table view when the UITableViewDelegate method:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
is called. However, knowing the index path isn't much use unless the cell also knows how many rows are in the same section so it can work out if it's the last cell or not.
I assume that the addition of the index path to the method dequeueReusableCellWithIdentifier: is to improve performance perhaps as #jrturton suggested with different reuse pools or simply to determine the position of a cell in grouped sections.
As far as I can remember from the WWDC videos, there were a few additional methods added in iOS 6 to support reordering, insertion and deletion of cells so perhaps this also comes into factor here?