Where does the indexPath of dequeueReusableCellWithIdentifier:forIndexPath: get used? - ios

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?

Related

UICollectionViewCell does load only when on view?

I'm developing an Chat application where I have a UICollectionView to control the messages and I came to a situation I would like to confirm with you.
For exemple, let's say I have 60 items in this UICollectionView, but based on the size of the items and the scrolling options I set, only the last 10 items are visible on the screen, from 50 to 59.
Based on that, it seems I'm not able to get cellForItem at IndexPath 30, for example. Is that correct?
I would like to confirm that with you before creating a solution to go over the items that are already "on screen" and I need to check. Any ideas and solutions you have already implemented is appreciated.
Also, based on the information above if, for example, I need to move on item from index path 30 to 31, will I have problems if they are not "instantiated" in the screen?
Thanks in advance!
You seem to be mixing your model, controller, and view classes, which is a bad thing™ for exactly the reason you encounter here.
I take it you're trying to access data from the index 30 (basically) and say to yourself "Hey, I already added that in the 30th cell, so I will just use the collection view's method to get that cell and take it from there". That means, you basically ask a view for data.
That won't work, because, as others pointed out (but more indirectly), there are not 60 cells at all at any given moment. There's basically as many cells as fit on the screen, (plus perhaps one or a few "buffer" cells so rendering during scrolling works, I can't remember that atm). This is why cellForItem(at:) is nil for an IndexPath that refers to a cell not actually visible at the moment. Basically it works in a similar way to a table view. The collection view simply does not keep around stuff it doesn't need to render for memory reasons.
If you need anything from a cell (which is after all also a view) at this path, why don't you get it from whatever data object represents the contents of this cell? Usually that's the UICollectionViewDataSource.
That's how the paradigm is supposed to work: The UICollectionViewDataSource is responsible for keeping around any data your app may need at a given time (this may or may not reloading it or parts of it, your choice). The UICollectionView uses its collectionView(_:cellForItemAt:) method when a certain IndexPath becomes visible, but it throws that away again (or rather queues it again so your data source may dequeue it in collectionView(_:cellForItemAt:) and reuse it for another data set that becomes visible).
And btw, please don't use use the UICollectionViewDataSource's collectionView(_:cellForItemAt:) method to get the cell and then the data from there. This method is supposed to be called by the collection view and depending on how you reuse cells or create them, this might mess up the entire process. Or at the very least create view-related overhead. Instead, get the data in the same way your UICollectionViewDataSource would get in inside of the method. Wrap that in an additional method you rely on or the like. Or, even better, rely on the model object that the controller uses as well.
Edit in response to your comment:
No, I did not mean it's bad to use a UIViewController as a UICollectionViewDataSource for a UICollectionView. What I meant was that it's bad to use the UICollectionView to get data, because that's what the data source is for. In your question you were wondering why cellForItem(at:) gives nil. That method is defined on UICollectionView. You didn't mention your intention was to move items around (I'll explain in a second), so I assumed you were trying to get whatever data was in the cell (I know, "assume makes an ass out of u and me...", sorry :) ). This is not the way to go, as the UICollectionView is not meant to hold the data for you. Rather, that's your job, and you can use a UICollectionViewDataSource for that. This latter class (or rather protocol a class can adopt) is basically meant to offer an interface for UICollectionView to get the data. It needs that, because, as said, it doesn't keep all data around. It requests stuff it needs from the data source. The data source, on the other hand, can manage that data itself, or maybe it relies on some deeper class architecture (i.e. other objects taking care of the underlying model) to get this. That part depends on your design. For smaller scenarios having the data source simply have the data in an array or dictionary is enough. Furthermore, a lot of designs actually use a UIViewControllerto adoptUICollectionViewDataSource`. That may be sufficient, but be careful not to blow up your view controller to a monstrosity that does everything. That's just a general tip, you have to decide on your own what is "too much".
Now to your actual intention: To move around cells you don't need to get them. You simply tell the UICollectionView to move whatever is at a given index path to some other index path. The according method is moveItem(at:to:). This works even if cellForItem(at:) would return nil for one of the two index paths. The collection view will ensure the cells are there before they become visible. it does so relying on the data source again, more specifically its collectionView(_:cellForItemAt:) method. Obviously that means you have to have your data source prepared for the move, i.e. it needs to return the correct cell for the given index. So alter your data source's internal storage (I assume an array?) before you move the items in the collection view.
Please see the documentation for more info on that. Also note that this is basically how to move items around programmatically. If you want the user to interactively move them around (in a chat that seems weird to me, though), it gets a little more complicated, but the documentation also helps with that.
Based on your question. If the currently visible cells on screen are from 50 to 59, the cellForItem at IndexPath 30 will not be available. It would be nil. Reason being the 30the cell would have already been reused to display one of the cells from 50 to 59.
There would not be problem to move cell from 30 to 31. Just update your array/data source and reload the collection view.
You can access the cell only if its visible for non visible cell you need to scroll programmatically using indexpath:-
collectionView.scrollToItem(at: yourIndexPath, at: UICollectionViewScrollPosition.top, animated: true)

UITableView use dequeueReusableCellWithIdentifier or not?

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.

How to add cell to tableview's reuse queue before 'cellForRow:'?

Creating a cell costs a lot of time and make the first scroll lagging, so I want to create a cell and add it to tableview's reuse queue before cellForRow: called.
I use dequeueReusableCellWithIdentifier: in viewDidLoad, but when I scroll the table, the cell is being created again.
In general all drawing methods of scrollview should be kept as simple as possible to avoid lag. This means you should prepare your data/model in viewDidLoad/viewWillAppear or even in previous ViewController. Your cellForRow should be as simple as set this image(s) and those text(s) - no checks, no expensive operations such as bluring, retrieving data from CoreData/Network, etc.
If you are not sure which thing exactly causes your lag, you should learn how to use TimeProfiler. If you feel lost in documentation, have a look this(quite outdated though) tutorial.
With thus said I cannot be able to help you anymore until you post some code which we could discuss.

UITableview cell reinitializing every time in iOS 7

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.

Load tableViewCell from Storyboard without using dequeueReusableCellWithIdentifier

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.

Resources