Load tableViewCell from Storyboard without using dequeueReusableCellWithIdentifier - ios

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.

Related

Name Label overlapping each time it is loaded

Our app has a social feed with a name label similar to Facebook with a FeedController and FeedCell class. Each time we scroll back to a previously loaded cell, it reloads, so the name label becomes darker, the text becomes bolder and starts to overlap. How do we fix this? We tried the prepareToReuse method and setting the cell's label to nil, but that did not help. Any help would be greatly appreciated!
It appears that you're regenerating the label and adding it to your cell's content view every time that the label is reused.
Inside your FeedCell class, make sure to only initiate your interface elements in your initializer. From there, you can fill your cell with data in the cellForRowAtIndexPath: method of your UITableViewDataSource.
Inside cellForRowAtIndexPath:, make sure to not add any views to the cell as this could contribute to the issue you're describing. As a rule of thumb, I make sure to initialize the views in the cell subclass's initializer, and only manipulate properties of these views (like hidden, alpha, etc) when working with the cells.
Moving forward, you can use prepareForReuse to clear any data from your cell that may be leftover from the previous time the cell was used. In many cases though, this isn't necessary as the data will be changed in cellForRowAtIndexPath:.

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.

Can I find out when a cell is queued?

I have a couple of things in my custom UITableViewCell's that I'd like to clean up before the cell gets queued. In a normal view, I'd put these kinds of calls in -(void)dealloc, but as they're being re-used rather than having an instance each, they won't dealloc. For now, I'd just like to print out a text in the log, NSLog(#"Cell out"); when the cell is queued, or "done".
I'm really just looking for something like -(void)didQueue in the cell-class, but my searches haven't shown anything.
I just found -(void)prepareForReuse, but I need more like prepareForQueue. I have to know when it's on its way out, not back in.
Random thought..: Searching for when the cell leaves the screen (or a bit after) might do exactly the same as what I'm looking for, but I'm thinking that costs a lot of processing..
For iOS 6+ the table view delegate will receive tableView:didEndDisplayingCell:forRowAtIndexPath: which tells you when the cell is not being used any more (irrelevant of queueing).
That said, prepareForReuse would generally be considered the correct place for the code you describe. If it isn't then you are probably assigning the cell responsibility it shouldn't have.

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

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?

Resources