I am setting up my UITableView using storyboard editor. For creating my cells I am using the standard delegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SearchResultCell"];
if (cell == nil)
{
// Do cell setup
}
// etc
return cell;
}
Except when the cell is dequeued the very first time it's not nil, as it should be. So the code inside the if statement is never executed.
People get this error when their reuse identifiers are inconsistent, so I went ahead and verified that I am using the exact same reuse identifier in my storyboard views as I do in my code. Still facing the issue. I also have several tableviews within the project and each one has a unique reuse identifier. Still no dice. Anyone know anything else that could be wrong here?
That's not how UITableView works anymore. Reading your question, I think you might be confused about how it worked before as well. If not, sorry, the first part of this is just review. :)
Without storyboard cell prototypes
Here's how it used to work:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// If the tableview has an offscreen, unused cell of the right identifier
// it will return it.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SearchResultCell"];
if (cell == nil)
{
// Initial creation, nothing row specific.
}
// Per row setup here.
return cell;
}
Here when you create the cell using the reuse identifier, you do only the initial setup here. Nothing specific to this particular row/indexPath.
Where I've put the Per row setup comment you have a cell of the right identifier. It may be a fresh cell, or a recycled cell. You're responsible for all setup related to this particular row/indexPath.
Example: if you set the text in some rows (likely) you need to set or clear it in all rows, or text from rows you set will leak through to cells you don't.
With storyboard prototypes
With storyboards, though, the storyboard and table view handle the initial cell creation! This is brilliant stuff. You map out your cell prototypes directly in the tableview when using storyboards, and Cocoa Touch will do the initial creation for you.
Instead, you get this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SearchResultCell"];
// You'll always have a cell now!
// Per row setup here.
return cell;
}
You're responsible for all the same per row setup as before, but you shouldn't need to write code to build your initial empty cell, either inline or in its own subclass.
As Ian notes below, you can still use the old approach. Just make sure not to include a cell prototype in the storyboard for the identifier you specify. The view controller won't be able to build your cell from the cell prototype, dequeueReusableCellWithIdentifier will return nil, and you'll be exactly where you were before.
Related
Hello guys i think almost everyone who is in ios development may come across the issue of reuse of the UITableCell by using following code line.
RZRestaurantListingCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
I have search lot about this but not getting any desire answer so please help me out in this case.
I have the same issue as most of iPhone developer having after reusing the cell.
I have the UIProgressView inside my cell and one button is there for downloading the video and i am showing the progress there in progress view how much is left.
So Now what i have problem is when i have more data and going out of the screen at that time i am press the download button on very first row of the UITableviewCell then i am scrolling down so the progress also shown in bottom random one cell so the UI changes in two cell rather then one.
You need to implement -prepareForReuse method in your custom cell class and set all cell properties to default value.
- (void)prepareForReuse
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:. For
performance reasons, you should only reset attributes of the cell that
are not related to content, for example, alpha, editing, and selection
state. The table view's delegate in tableView:cellForRowAtIndexPath:
should always reset all content when reusing a cell. If the cell
object does not have an associated reuse identifier, this method is
not called. If you override this method, you must be sure to invoke
the superclass implementation.
Refer here for more, https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/#//apple_ref/occ/instm/UITableViewCell/prepareForReuse
You need to assign a progress value inside the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RZRestaurantListingCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// If the cell is reused, the `-prepareForReuse:` of `UITableViewCell` will be called.
//!! Assign current progress value to this cell, otherwise, the progressBar.value may look like a random value.
//!! Because the current cell is reused from a disappeared cell.
cell.progressBar.value = ... ;
return cell;
}
The design may be complex, because the progress may be updated continuously when the cell is on the screen.
Use prepareforreuse method to clear content of cell before using it... e.g.
-(void)prepareForReuse
{
[super prepareForReuse];
self.textLabel.text = #"";
self.detailTextLabel.text = #"";
self.imageView.image = nil;
}
I'm a little confused to why an identifer (*MyIdentifier) is always required. The code below demonstrates this. I have noticed all tableviews require at least one.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
}
What is the purpose behind having an identifier? Ive have seen a few tutorials where there is more than one. Also, reading the Apple documentation, I was a little confused about why the following is called:
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
}
I would like to know why it takes the input of 'MyIdentifier'? Assuming we had more than one identifier, which one should we choose? To be exact, what if we had a Parent, Child and sub-child etc.
Suppose you have several customized UITableViewCells and each of them have different backgound colors. In your storyboard or xib file, you may name the cell with red background color "redCell" and the one with blue "blueCell". Then you can select what kind of cell to add to a particular row using their identifiers. Let's say, you wanna apply red cells to odd rows and blue ones to even rows then you can use the following code to do this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *redCell = [tableView dequeueReusableCellWithIdentifier:#"redCell"];
UITableViewCell *blueCell = [tableView dequeueReusableCellWithIdentifier:#"blueCell"];
if (indexPath.row % 2 == 0) return redCell;
else return blueCell;
}
Without specifying an identifier, the system wouldn't know which kind of cell to pick.
From the docs:
The identifier is a string identifying the cell object to be reused. This parameter must not be nil.
For performance reasons, a table view’s data source should generally reuse UITableViewCell objects when it assigns cells to rows in its tableView:cellForRowAtIndexPath: method. A table view maintains a queue or list of UITableViewCell objects that the data source has marked for reuse. Call this method from your data source object when asked to provide a new cell for the table view. This method dequeues an existing cell if one is available or creates a new one using the class or nib file you previously registered. If no cell is available for reuse and you did not register a class or nib file, this method returns nil.
Every time you return a cell you have 2 options - you can create new cell and return it, dequeue cell that already exists and reconfigure it.
So the way this works is when you create cell you give it reuse identifier, so when cell goes off screen it can be used instead of creating new cell. After the cell is dequeued though, you might want to change its properties(like text or image)
You can have different reuse identifiers for different cell types(different content views)
In this project I have absolutely no code. In my main.storyboard I have a TableViewController with so far 1 cell. In that one cell I put an Image View in, then I selected the certain picture. The cell is 320 (Width) by 44 (Height). Everything seems fine in the main.storyboard, however I press run, the app opens but there is nothing there, only a bunch of lines.
Any help would be great I will give screenshots to anyone who needs them in order to help me solve this problem. I am using xCode 5.1
If you don't want to write any code than you must tableView type to static!
Check that.
Here's the snippet for TableViewDelegate. Hope this will help. Don't forget to thumbs up
Make sure you have this in your TableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1; ---->> should return 1
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 10; ----->> return how many rows you want to show, mostly you write
return yourarray.count;
}
I think the problem will be in the method below
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = nil; ---->> Instead of UITableViewCell you need to
change to the class name of the
TableCell ( This is where your cell is
created)
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:#"Cell"]; ---->>Instead of UITableViewCell you need to
change to the class name of the
TableCell ( This is where your cell is created)
}
cell.textLabel.text = #"Cell";
return cell;
}
You have to use either a static tableview that uses predefined cells or (by default) use a dynamic tableview - in that case you have to provide the cells in code by adhering to the tableviews data source protocol. Which you seem to not be doing now.
So, in interface builder, set the tableview to be static and you can build the cells in interface builder and they will be displayed. Make sure you delete the boilerplatecode in table viewcontroller that is given to you to easily implement the protocol (if you don't delete it, the default code returns nil off the cells and so none will get displayed).
When you get it working, find a nice tableview tutorial. I suggest ray wenderlich or the cs193p course on itunesU.
Ahoy!
I'm trying to create a reusable UIView (for various reasons) similar to the UITableViewCell implementation used in UITableViewController. I'd like to use the reusable view in a UIScrollView so I know i'm not trying to achieve something that's entirely unattainable.
The default implementation of this is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//declare cell identifier
static NSString *cellIdentifier = #"cell_identifier";
//dequeue cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//check cell is valid
if(cell == nil)
{
//create a new cell
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//
//return cell
return cell;
}
From this, it's worth noting that the cell is dequeued from the UITableView. If the cell is invalid, a new cell is created. My question is, how does this cell then become "queued" for reuse later?
My current attempted implementation looks like this:
- (TestScrollViewCell *)scrollView:(TestScrollView *)_scrollView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//declare cell identifier
static NSString *cellIdentifier = #"cell_identifier";
//dequeue cell
TestScrollViewCell *cell = (TestScrollViewCell *)[scrollView dequeueReusableCellWithIdentifier:cellIdentifier];
//check cell is valid
if(cell == nil)
{
//create a new cell
cell = [[TestScrollViewCell alloc] initWithFrame:CGRectZero];
}
//
//return cell
return cell;
}
I'm thinking that adding a NSMutableDictionary to my TestScrollView to store the cellIdentifier and the TestScrollViewCell (UIView) and then plucking them back out based on the dictionary key would be a good start but is this really a true implementation of "reusable" cells?
The issue I can see is that I would then be adding the UIView to the ScrollView which is positioned based on the frame. Dequeing a view in this sense wouldn't allow me to then add the view to the scroll view without affecting the first view (by modifying the frame) but surely this is how UITableViewCells work, as well as section headers/footers?
I've been looking at this implementation which seems to be following the same route I was intending on implementing but i'm not 100% sold that this is a true implementation of reusable cells.
Has anyone had any luck with this previously? I'm trying to take Apple's lead on this one but other than UITableViewCell and MKAnnotationView (MapKit) there aren't any accessible implementations of this for me to glean from.
Any help would be greatly appreciated.
It's not just the view, it's the whole UITableViewController you'll need to recreate. The reuse flow goes like this:
dequeueReusableCell gets empty reused cell from some storage, I guess, from NSMutableArray (grab first object from array, then delete it from array and return it). If array is empty, method returns nil. You check for cell value, if it's nil, you create a new instance of cell class. If it's not nil, you fill it with your data.
This goes for every visible cell, that is, every cell that can fit on screen. Any non-visible cells are not initialized. When user scrolls the table, cell that are gone completely off-screen (not a single pixel visible) sent to reuseQueue – all their subviews and values return to default values or just nilled, and then cell gets added to the end of our NSMutableArray that is the queue.
I hope I explained well enough.
EDIT: Oh, and one more thing - you'll need different reuse queues for each reuse identifier.
I have a UITableViewController with UITextfield inside the tableview cells. If I scroll the table view, the user entered data in the textfields disappears. I tried to add the textfield data to a NSMutableArray but it still didn't work. Any help please.
When cellForRowAtIndexPath: is called, the cell you return has to be completely filled in with whatever data you want to show. So, if the cell includes a UITextfield, you'll need to set it's text property to the right value for that row in your data.
When a table cell disappears off the top or bottom of the screen, the UITableViewCell itself becomes available for re-use. (As you scroll, cells disappear, and new cells appear, but the UITableView class is re-using the UITableViewCell objects.) In cellForRowAtIndexPath: when you get a cached cell to use, you have to be sure to setup everything you want it to show for the row in question, otherwise you might see some odd behavior in your table.
Does this help?
EDIT:
Here's an example of the typical pattern used in cellForRowAtIndexPath:. Notice the use of dequeueReusableCellWithIdentifier:. That method returns a previously allocated but not in use UITableViewCell, if there is one. Notice further that if no cached cell is returned, the code creates a new one, and sets it up (with stuff that is independent of anything that might be row specific). Following that, you'd setup the cell as you need it for the row in question.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SearchResultsCellIdentifier = #"SearchResultsCellIdentifer";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:SearchResultsCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:SearchResultsCellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Your row-specific setup of the cell here
// ...
return cell;
}
Check the docs for specifics about these methods. There are LOTS of examples from Apple and elsewhere about how to implement tableViews.