Reusing custom UITableViewCells, see old UIView pictures briefly before new pictures load - objective-c

I have a custom UITableViewCell that contains a UIView, in which I display a couple of pictures.
In my tableView:cellForRowAtIndexPath: function, I call dequeueReusableCellWithIdentifier: to reuse old custom cells, and then I update them with new pictures.
The problem is that when I scroll quickly on my screen, I see flickers of the old pictures before the new ones are loaded, which is unattractive.
I've tried to fix this by:
implementing prepareForReuse in the custom TableViewCell implementation file; this led to the same three UIViews appearing over and over again, and in this case the new pictures stopped loading altogether
clearing the UIView right after calling dequeueReusableCellWithIdentifier by using a for loop to remove all subviews; the app now takes a really long time to load pictures.
What is the best way to fix this, and why do the above errors in my attempted fixes occur? Here is my current code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"Blah";
blahCell *someCell = (blahCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!someCell) {
//initialize cell
} else {
someCell.imageContainerView = nil;
}
... (other code here)
}

I'd try setting the cell's imageView property to nil as the first step in your table view data source method cellForRowAtIndexPath.

You can use this UIImageView Category by AFNetworking.
Whenever you have to set a new image you just need to call
[someCell.imageContainerView setImageWithURL:url placeholderImage:placeholderImage];
Now, whenever you scroll, if the image is not loaded you will see a placeholder image instead of seeing previous cell images.

Related

Registered NIB, still getting assertion error when I use dequeueReusableCellWithIdentifier

I have a custom UITableViewCell class that I want to use to create custom table cells. I created the custom table cell's xib as well as its header and implementation files, all called RTRepairOrderTableCell.m/.h/.xib.
My issue is that even though I set the reuse identifier of the table cell to RTRepairOrderTableCell inside of the .xib file and registered the xib inside of my table view controller, I am still getting assertion errors when it tries to dequeue or create a new cell for use.
Inside of my view (table) controller I have the following:
- (void)viewDidLoad
{
[super viewDidLoad];
//Load the nib file
UINib *nib = [UINib nibWithNibName:#"RTRepairOrderTableCell"
bundle:nil];
// Register this Nib, which contains the cell
[self.tableView registerNib:nib
forCellReuseIdentifier:#"RTRepairOrderTableCell"];
}
There are no errors here and it finishes viewDidLoad just fine.
Inside of my cellForRowAtIndexPath I have the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RTRepairOrderTableCell *cell = [tableView dequeueReusableCellWithIdentifier:#"RTRepairOrderTableCell" forIndexPath:indexPath];
return cell;
}
According to every tutorial I've seen, this should work so long as I have the reuse identifier set properly inside the xib file and I register that xib inside of viewDidLoad in the view controller class that will display the table cells, so I am at a loss as to why I am getting
*** Assertion failure in -[UITableView _dequeueReusableViewOfType:withIdentifier:], /SourceCache/UIKit/UIKit-2935.138/UITableView.m:5413
Turns out I had a few orphaned UIImage objects inside the nib file but outside of the UITableViewCell area, so it was throwing errors saying that the UITableViewCell needed to be the topmost view.
I faced this and it was driving me crazy. But it was my fault. I had set identifier #"Abc" in Nib file and I was registering the same nib file with some other identifie #"Xyz". Removed the identifier from nib, left that part blank and it worked like a champ. :)
This might be a rare case, but due to some sloppy copy pasta two different UITableViewCell custom classes used in my table had the exact same Identifier specified in the Xib.
Switching one of these cells to have a different Identifier in the Attributes Inspector fixed my Assertion failures.
Check if in your .xib there are no UI component outside main layout:
Label is not accepted
In this example, Label is not accepted and cause the exception

Xcode Table View erased after assigning custom class to controller

I am using XCode 5, and I am having an issue with assigning class to a TableViewController
I made a form using tableViews within a TableViewController. The TableView has static cells that are the ones containing the textBoxes for user input.
The problem is that after designing the tableView, I added a custom class to it. When I run the program, the view is an empty table view, but in the storyboard looks like I design it.
So maybe after assigning the custom class (which is a new empty class UITableViewController) everything is either not being load or being re-instanced?
Added:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
return cell;
}
This is the default method.
You need to create custom class for UITableViewCell, instead of creating it for UITableView, as table view contains tableViewCell, as the default ones, but your cell contains textFields which is not by default. Let me know if you need more clarity here
None of the above helped me. This is what solved the problem:
What was going on? the problem was that even with Static Cells instead of Prototypes, the TableViews was trying to use his datasource to get the cells to display (The class that i assigned to its controller). It was easy then, i just had to erase the methods in the TableViewController Class.
Maybe because the class had an function that weren't suppose to be there, or because the TableView had the DataSource linked to it.
The result was a TableViewController with a static table.

UITableViewCell as Header or Footer View

I know about UITableView reusable header and footer view
but in my case, i have UITableView Cells, which i need to place also in section headers and also in normal rows
if i use
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
MyCell * cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
// ...
return cell;
}
How does it work out with the reusing? (is the message to be available for reuse even than passed), or does this disable the cell reuse
The cells get dealloc'ed when they go off-screen. So they don't get reused. An easy way to verify this is to subclass UITableViewCell with the following
- (void)dealloc
{
NSLog(#"I got dealloc'ed");
}
and observe the console output as you scroll.
These has always worked fine. You first should create a prototype with that name, or register a custom nib with your custom section identifier. HOWEVER , I noticed this breaks in iOS 7 when you add new sections to the table dynamically. Reverting to a plain non-reusing UIView works. Really a shame!

Why dequeue reusable cell twice in making custom table view cell

I am following a tutorial of making custom table view cell with storyboard. I drag a UILabel as subview of the cell and set its tag to 1. I have two questions regarding the data source code.
What's the purpose of the second dequeue statement? I know it's an init method while not using storyboard to make the custom cell.
What's the difference between tableview and self.tableview?
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
NSDictionary *dToAccess = (self.tableView==tableView)?[self.arForTable objectAtIndex:indexPath.row] : [self.arForSearch objectAtIndex:indexPath.row];
[(UILabel*)[cell viewWithTag:1] setText:[dToAccess valueForKey:#"name"]];
[(UILabel*)[cell viewWithTag:2] setText:[dToAccess valueForKey:#"value"]];
return cell;
}
For your first question, the second dequeueReusableCellWithIdentifier: looks like a mistake.
Here is how a UITableView works:
You might have 50 rows in your table, but if only 10 rows are visible at a time, you only need to make 10 cells, and then when the user scrolls, you can reuse cells that have gone offscreen instead of always releasing them and init'ing new cells that come onscreen. A UITableView keeps a list of cells that have gone offscreen and when you call dequeueReusableCellWithIdentifier:, it removes it from the list of offscreen cells and returns it to you. From here you can customize the cell for re-use (change its text, color, etc) and return it. Again, this is not an "init" method, this is returning a pre-existing cell.
So, let's look at what happens when this UITableView is first displayed -- in this example there are 10 visible cells, so the tableView will call tableView:cellForRowAtIndexPath: 10 times to get cells to display in these 10 slots. Every time this is called, you will need to initialize and return a new UITableViewCell to display. (At this point dequeueReusableCellWithIdentifier: will return nil, because you don't have any offscreen cells to re-use yet)
When a user scrolls your list, cells will begin to go offscreen, and new cells will need to appear. You don't need to make new cells, because you have already created as many as will need to be onscreen at a time. You should call dequeueReusableCellWithIdentifier: to get a reference to a cell that has gone offscreen, which you can then re-use.
I would alter your code like this:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: CellIdentifier] autorelease];
}
Now you are checking for reusable cells before creating new ones.
For your second question,
In your example, tableView refers to the tableView that was passed in (see the "tableView" in your method signature). Separately, if your class has defined a property called tableView, then self.tableView will call the getter for this property.
When apple developed the UITableView for the first iphone they had a problem in performance when scrolling through it. Then one clever engineer discovered that the cause of this was that allocation of objects comes with a price, so he came up with a way to reuse cells.
dequeueReusableCellWithIdentifier method is used to returns a cell if it has been marked as ready for reuse.
So Whenever there are many number of rows in a table view and you are going to scroll it, then the cells which are just passed away from your previous screen before scrolling are get reused instead of creating new one.
And to know the ans of your second que. I think you should refer this link :
http://www.iphonedevsdk.com/forum/iphone-sdk-development/17669-when-use-self-objectname-just-objectname.html
To dequeue twice is not necessary, this block of code is broken.

Search result of UISearchDisplayController has other cell layout and behaviour than searched table

I am using storyboarding. I have an UITableView with one prototype cell. This cell is of style "subtitle". I have added a segue from the cell to the detailed view. So when the user taps a cell it will open the corresponding editor... That all works great.
Now I added a UISearchDisplayController an a UISearchBar, implemented the delegates. That works very well.
But in the search result table the cells are of style "default" and are not tapable. What do I have to do to get a result table looking and behaving like the "unsearched" table?
I would like to contribute for answer #1 this is what I did and it worked for me
in the method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
instead of assigning the cell from the parameter tableView
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
assign it directly from the TableView on the view so you have to replace this
// UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
with this
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Found the problem...
The method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
pulled the cell from the tableView, which is in the result case not the tableView from the storyboard but the resultTableView from the SearchDisplayController.
I now get the cell to display in both cases from the table view in the storyboard and now it works.
I've been using ios 7.0 and Xcode 5.0. I found that search display controller is using the same tableview layout as the delegate view controller. All you have to do is judge if the current tableview is the delegate view controller's tableview, or the search display controller's tableview. But remember to add the sentence
tableView.rowHeight = self.tableView.rowHeight;
in the following code snippet:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (tableView == self.searchDisplayController.searchResultsTableView)
{
tableView.rowHeight = self.tableView.rowHeight;//very important!
return [self.searchResults count];
}
else
{
...
return ...;
}
}
if you forget to implement that sentence, then the row of the table view of search display is only as high as a default row, which makes you think it doesn't look like the "unsearched" table.
There is a possible answer to this here. It may not work entirely for you, but as I explained, the UISearchDisplayController creates the table view.
Check the documentation and you can get a better understanding of it, but it states:
You initialize a search display controller with a search bar and a
view controller responsible for managing the original content to be
searched. When the user starts a search, the search display controller
is responsible for superimposing the search interface over the
original view controller’s view and showing the search results. The
results are displayed in a table view that’s created by the search
display controller. In addition to the original view controller, there
are logically four other roles. These are typically all played by the
same object, often the original view controller itself.
In my case UISearchDisplayController was using right cell type (custom) but height of cell was wrong so I had to use
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
method to fix it.