how to fix an issue with custom subclass of uitableviewcell reuse? - ios

Before I get crucified for this, let me state... Yes, I have read every "relevant" answer on this topic and have not found a workable solution. Most "correct" answers are pre-ARC and discuss "releasing" a cell, which just isn't done anymore. Secondly, my problem is not "global", meaning some views have no problems, while others do. So here is my question...
I have sub-classed uitableviewcell and setup some uilabels & custom uiviews. From there I wired everything up in ib (Xcode 5.x iOS 7.x). Once I put in the appropriate code and create the tableview & dynamic cells from a nsarray "not mutable" everything works exactly as expected with no issues.
This is the fun part. I am making changes to allow the data source of the tableview, which is an nsarray to be mutable to allow adding and removing of items / cells. This is where things get hairy. When I start to add more objects to the array and when the reuse cell is being put on screen visual data from old cells is being reused on new cells. I say "visual" because once the cell is selected the view updates to display the correct information. The part that is interesting is that as I stated I have some uilabels which never have any problems being redisplayed, my custom views however are now the piece of the puzzle that is displaying info from past cells, and when scrolling back up, the original cells no longer display the correct information. Once the cell is tapped, then the cell updates and displays the correct information.
the most confusing bit of this is that before my array was mutable and had a static amount of objects this worked fine. Even if a cell went off screen and came back, it was still the correct information being displayed. Now I know that shouldn't have anything to do with it, but it is strange that it worked using the same tableview & cell code that I am using now.
I have tried adding in
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
if (!cell) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"]; // note: obviously as stated, tacking on "autorelease" here as mention in other suggestions is not going to work.
}
Which doesn't fix the issue.
I tried overriding the "prepareForReuse" method on my custom cell subclass and that does not resolve the issue either. I have made the views, "strong" & "weak", and all that and still every 3rd or so cell gets repeated with garbage data until it is refreshed. Again, the uilabels which are setup the same way as the views have no problems and data is never reused. I would say there is a problem with my custom views, but setting up the table from a static source of identical information there is no problem.
I would like to post some code, but it's all pretty generic code for tableviews & delegates. any suggestions would be greatly appreciated.
As i said the code is all pretty generic, but apparently it needs posing anyway so here it is..
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyThing *thing = self.stuffArray[indexPath.row];
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.thisLabel.text = thing.someText;
cell.thatLabel.text = thing.otherText;
cell.view1.someProptery = thing.object1.property
cell.view2.someProptery = thing.object2.property
cell.view3.someProptery = thing.object3.property
//"someProperty" on "view..." is an NSInt that is used to determine custom drawing in the view.
return cell;
}

I think the key to the solution lies in your comment about the custom views in the cells. If cellForRowAtIndexPath is altering the states of those views, they need to know that they must be redrawn, so you'll need to augment the synthesized setter in your custom view.m that has someProperty.
If the someProperty determines how this view get's drawn, then it's incumbent upon the setter to indicate that the view is out of date....
- (void)setSomeProperty:(NSInteger)someInt {
_someProperty = someInt;
[self setNeedsDisplay];
}

Related

UITableViewCell is redrawing on dequeue

My setup
I have a UITableViewCell that is in my main storyboard in a UITableViewController. It gets populated with some JSON data pulled from a REST API that will cause each cell to be a variable height. There are UIImageViews, UILabels all of different heights and styles, think Instagram-esque.
My problem
When I scroll to maybe the 5th or 6th cell, then go back up, they start redrawing and overlapping, so text gets mixed, lines get redrawn, etc.
What I've tried
This seems like a common problem on SO, so I've tried several posted solutions. It seems like my issue is probably the same problem as others face, which is, I am calling addSubview on my cell every time it dequeues, but I've tried checking to see if the cell already exists. I came across another post somewhere (sorry, I can't remember where), that suggests that because I am creating this in the storyboard, it is already initialized and if ( !cell ) will already return false, so I don't know how to prevent it from redrawing.
When I try removing the cell from the storyboard, and creating it programmatically, I get an error saying it can't find a cell with my identifier #"Cell".
I've also tried someone's solution of removing all subviews when I dequeue, so I used:
for ( UIView *view in cell.contentView.subviews ) {
if ([view isKindOfClass:[UIView class]]) {
[view removeFromSuperview];
}
}
and it doesn't find anything.
#rdelmar's comment is correct. You shouldn't do what you're doing. Might work, but it's bad form and you don't want to get into bad habits.
First, take advantage of object oriented programming. A cell should be able to configure itself based on the data you ask it to display. The table view shouldn't be designing the cell.
UITableViewCells need to be optimized for speed. Creating and adding subviews is a slow process. It's OK to do it once, but the cell will be reused (a system optimization) and you should just reuse the existing views that were added the first time the cell was created.
For example, you can hide subviews if they're not needed. You might want to do this in -prepareForReuse. You can move them around in -layoutSubviews. Or change the position of subviews in -updateConstraints.
Typically you just want to pass the data to display to the table view cell subclass from the data source (often the view controller). Let the cell do the display work.
When you add your subview after dequeueing uour cell, give a tag to your subview. This way, when you dequeue a cell, you can first check for the presence of a subview with your tag, and if it exists, remove it before adding your new view:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
// try to dequeue a cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<yourCellIdentifier>];
if( !cell )
{
// create a new cell if necessary
}
static int _myViewTag = 1000987 // give it a high int : low value are used by the system in cells
UIView *v = cell.contentView viewWithTag:_myViewTag];
if( v ) // subview with such tag already exists, so remove it.
[v removeFromSuperview];
// now add your new subview
[cell.contentView addSubview:<yourView>];
// adjust height of cell to your view.
...
}
Try to add a new method in your cell class to reset cell to its default style and call this method after dequeueCell.
The most efficient way to manage this is to subclass UITableViewCell and adding all your required Views as properties. So now when a cell comes up for "recycling", you know where to find the old views, like :
[cell.myTextLabel setText:#""];
aaaand you're done.
UPDATE creating a subclass makes sense if you have only a small number of "TYPES" of cells. create a subclass for each. How much complicated it gets depends on your specific scenario. But i've done it and found it to be the most effective method.
UPDATE 2 or you could make multiple cells in the storyboard, and dequeue the appropriate one based on the data source, save all the coding.

iOS todo-tutorial, can't repopulate tableview

So yesterday i began learning iOS programming, and i followed the tutorial for the to-do app.https://developer.apple.com/library/ios/referencelibrary/GettingStarted/RoadMapiOS/SecondTutorial.html#//apple_ref/doc/uid/TP40011343-CH8-SW1
I have created my own tablelistviewcontroller as the guide tells me, and all my previous static data is gone when i boot, and the tutorial tells me this
you’ll notice that it implements three methods—numberOfSectionsInTableView:, tableView:numberOfRowsInSection:, and tableView:cellForRowAtIndexPath:. You can get your static data to display again by commenting out the implementations of these methods. Go ahead and try that out if you like.
So i commented out the cellForRowAtIndexPath, it was the only one commented out. And then i got an error at the "reuseIdentifier", so after a time of googling, i have managed to name my cells "Cells" and i ended up with this code,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cells";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
return cell;
}
But everytime i build and start the app, i cant see my previous data, i can see it in my storyboard, but not in app.. And now i am out of ideas and since i am a newbie i believe the solution is pretty easy.
Please notify if you need any additional data.
I think you really misunderstood these lines:
So why doesn’t your data show up? Table views have two ways of getting
data—statically or dynamically. When a table view’s controller
implements the required UITableViewDataSource methods, the table view
asks its view controller for data to display, regardless of whether
static data has been configured in Interface Builder. If you look at
XYZToDoListTableViewController.m, you’ll notice that it implements
three methods—numberOfSectionsInTableView:,
tableView:numberOfRowsInSection:, and
tableView:cellForRowAtIndexPath:. You can get your static data to
display again by commenting out the implementations of these methods.
Go ahead and try that out if you like.
The thing is when you are adding data using your app, the data is no more static. It is dynamic. UITableView doesn't store your data. It just displays it. And when you are using dynamic data, you will need a datasource which will give the tableView the data to display. You must store the data by some possible storage mechanisms in IOS. Here is a link to one such tutorial which will get you started with Core Data.
Here is a good tutorial which will get you started with UITableView that will display a dynamic data.
The problem is you got the concept totally wrong.
EDIT
You might have added something like <UITableViewDataSource> in the interface file and may have added the ViewController as the datasource for the table(either using ctrl-drag in story board or tableView.datasource = self; in the code). Just remove these two and your static content will display again.

invalid nib registered for identifier (CELLNAME) - nib must contain exactly one top level object which must be a UITableViewCell instance

Sorry for the long title, but I wanted it to be clearly seen from a google search.
Also, this differs from many of the other similar questions on here as its not specifying a 'null' identifier but one that actually exists.
Essentially the problem occurs when your trying to navigate using one of the cells in a table view controller to segue to another view (Whatever that view may be).
Most people would run into this problem after gunning through the apple tutorial on the 'To-Do List' and expecting the cells to operate in the same motion regardless of their purpose.
This problem is probably simplistic to most but for a beginner, its quite hard, it took me around 3 hours.
Basically the error is:
invalid nib registered for identifier (prototypeCell) - nib must contain exactly one top level object which must be a UITableViewCell instance
Where 'prototypeCell' would be whatever your cell is called. This is an exception that occurs immediately as the app is launched.
I had the same problem as above but I wasn't using storyboards and the problem just appeared out of the blue.
I found that the solution was in the tableview cell file. I had added a uibutton, but it had been added outside the bounds of the cell by mistake. This meant it was almost like an extra view in the uiview.
Once I found it and deleted this extra view the problem disappeared immediately.
If you have having this error, check the uitableviewcell for extra views and objects added by mistake
The answer of simon_smiley pointed me to the right direction, but some more trial-and-error was needed leading to the following solution:
The problem doesn't only occur for additional top-level UIView objects, but also for gesture recognizers. So make sure not to use any gesture recognizers in your failing XIB file, you can set them up in code instead.
For example you can do it in awakeFromNib as noted by vahotm in the accepted answers comments.
I had the same problem! And in my case custom cell was the subclass of UIView by mistake instead of UITableViewCell. So replacing UIView with UITableViewCell fixed the problem!
Same problem because I drag and drop a UITapGestureRecognizer on the subviews of ContentView. Just remove it.
In my case, I had an extra ImageView inside the xib added by mistake. Removed it and everything worked perfectly.
The problem is that there are multiple cells in your storyboard that have the same name. Such as for a single table view, there are multiple cells that have the same identifier. In my case I had three cells that were all called 'prototypeCell'.
The fix is actually quite easy. For every cell, name it a simple name with the cell number at the end. This cell number has to match the indexPath.row later on, so basically start from 0 onwards.
For Example:
prototypeCell0
prototypeCell1
prototypeCell2
Then, go into the class thats responsible for the controller, and find the function
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Then replace the code:
static NSString *CellIdentifier = #"PrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
With the code:
static NSString *CellIdentifier = #"ListPrototypeCell";
NSString* num = [NSString stringWithFormat:#"%d", indexPath.row];
NSString* actual = [CellIdentifier stringByAppendingString:num];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:actual forIndexPath:indexPath];
Everything else can stay the same. This code basically gets the row number, adds it to the identifier base, the gets the cell from the table using that string. So if its responding to a different row number, it return a different cell.
Just for clarification, my whole function for the problem is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListPrototypeCell";
NSString* num = [NSString stringWithFormat:#"%d", indexPath.row];
NSString* actual = [CellIdentifier stringByAppendingString:num];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:actual forIndexPath:indexPath];
cell.textLabel.text = self.content[indexPath.row];
return cell;
}
Good Luck!
Also, this is my first Answer/Question combo, so I really have no idea how to approach it, so if i've done anything wrong, please tell me and/or change it.
Thanks!
I'll put up my dumb solution for the sake of googlers...
This was my personal dumb mistake - when I created a new custom cell for my table view, I went to the top level directory of my project -> add new file -> and created an Empty File type under the OS X section rather than the iOS section.
Hopefully, most people have the other issue described above since it's less embarrassing :P
Sometimes you using storyboard and have collectionView inside it and collectionView as well. After that you decide to simplified your Storyboard and divide cell into another nib. You create empty nib, Ctrl+C from storyboard -> Ctrl+V into nib.
Everything looks fine but you'll have Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'invalid nib registered for identifier (PrettyCollectionViewCell) - nib must contain exactly one top level object which must be a UICollectionReusableView instance'
Ansver: do not do this. After I clean nib and add all of elements as I've in Storyboard - it fixed.
Seems like Bug of xCode IB - Version 7.3.1 (7D1014)
This error is mainly due to some extra views that are added by mistake. Have a look in the .xib file and check for extra unwanted view added by mistake. Delete that and it will work perfect. You can check for unwanted views by this is how it looks
In my case,
I have added UITableViewHeaderFooterView subclass and XIB in different target instead of actual target.
Make sure it is in running target.
I solved this by setting the class name in interface builder to match the cell reuse identifier (for that class) in interface builder.
Note: I'm not saying the class and identifier have to be the same. I'm saying they need to link the corresponding view and backing model.
I had this issue, finally found out that i had created UITableViewCell Subclass instead of CollectionViewCell subclass. it was evening, and i was tired,lol. Fixed it in the mng.
I dragged collection view cell from other project and got this.
'invalid nib registered for identifier (cell) - nib must contain exactly one top level object which must be a UICollectionReusableView instance' .
Then i compared the collection view cell from created my me and found reusable view missing. Hope this help.
It was a silly mistake on my side and I had two tableview cells which caused this error. Removed the other 'table view cell' which fixed the issue.

Understanding UITableView dequeueReusableCellWithIdentifier - returns cell even on the first call

I'm having trouble understanding how this works. I've read many threads on SO about it - such as UITableView dequeueReusableCellWithIdentifier Theory and How does dequeueReusableCellWithIdentifier: work?.
However, my UITableView succesfully dequeues a cell each time (it's never nil), even when it first loads. I was under the impression that similar cells should use the same identifier, so you only have to change what's necessary.
Because
if (!cell) {
NSLog(#"New cell");
cell = [[UITableViewCell alloc] initWithStyle:someStyle reuseIdentifier:someIdentifier];
}
Never gets called, I'm not sure how I'm supposed to handle cells in the same table with different styles, because the style can only be set in the initializer.
I also tried using different cell identifiers, to make sure it wasn't reusing cells from a different table or something. I am registering these identifiers with [tableView registerClass: forCellReuseIdentifier:]
If I understand, this method should only return previously created cells that have been moved off the screen (hidden, i.e. can be reused). Then how come it returns a cell the first time it's called?
Edit: So the confusion was using [tableView dequeueReusableCellWithIdentifier: forIndexPath:] instead of [tableView dequeueReusableCellWithIdentifier:] (the first requires registering the identifier, the second will return nil if none is available - the behavior I was expecting above).
However, I noticed that when I changed my code to use [tableView dequeueReusableCellWithIdentifier:], it creates a new cell, and its contentView.frame has a width of 320 (full width). Before, when I did dequeue...forIndexPath it would give a width of 302, or the visual/"real" width of the cell. Why is this?
Also, is there a way to specify the style of the UITableViewCells regstiered for reuse?
Solution: So I found this thread UITableView cell.contentView.bounds.size.width Changes With Cell Reuse, which says when you set the autoresizingmask to UIViewAutoresizingFlexibleLeftMargin, it's fixed when you try to do relative positioning (the contentView width is initially the fully width, but when you present it it's shrunk, so if you do your calculations right it'll still show up properly).
I was positioning a UISwitch on the right - and when I set the autoresizing mask it works when it's first displayed but shifted over another ~20 pixels when I switched it. I don't know what caused that extra shift, but I ended up solving it by simply setting the UISwitch as the cell's accessoryView.
(This is partially off topic from the original question, but if someone stumbles on this maybe it'd be useful). For anyone wondering specifically about the original question, the answer is under the first edit.
When you call [tableView registerClass: forCellReuseIdentifier:], you're teaching the table view what to do when you later use the specified ReuseIdentifier. So, when you later call [tableView dequeueReusableCellWithIdentifier:] it will either:
A. Get a cell that has previously been created and isn't currently being used
OR
B. Create a new cell of the class you specified
So, when you dequeue, you will always get an instance. If you want to create new cell instances yourself with initWithStyle:reuseIdentifier: then you shouldn't register a class with the table view. Alternatively, leave the registration and add logic to specify everything that needs to be configured (and consider using multiple different cell classes and reuse identifiers).
because the first time the cell is nil that is why this gets called:
if (!cell) {
NSLog(#"New cell");
cell = [[UITableViewCell alloc] initWithStyle:someStyle reuseIdentifier:someIdentifier];
}
but then if the cell is already ready for reuse and basically its not nil - it returns the cell and it does not hit the above if statement
From the apple docs at https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006943
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.
The dequeue method will
Return a recycled cell if one is available
Create a new cell if you registered one (you mentioned you did this)
If none of these are true, it returns nil
I'm guessing if you remove the registration (which may be hidden in a xib) then you will get the nil result.
if you see UITableView.h
Beginning in iOS 6, clients can register a nib or class for each cell.
If all reuse identifiers are registered, use the newer -dequeueReusableCellWithIdentifier:forIndexPath: to guarantee that a cell instance is returned.
Instances returned from the new dequeue method will also be properly sized when they are returned.
(void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
(void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);

Xcode 4.5 UITableView

I have a question regarding UITableView on Xcode. I have text in the cell but when I test it, it doesn't show. I just started programming so any help would be appreciated. If anyone can email me so I can provide the project file that would be most helpful.
You will need to be aware of how table views work. I would suggest carefully reading some of the excellent online documentation on this topic, including sample code if available.
You will need to remember that the system is responsible for deciding which parts of the table view are supposed to be visible, then asking your code to provide the details of what should be seen.
If you have actually provided the routine to provide the contents of the cell, try putting in a breakpoint or some logging to ensure that your code is being called. If so, start by returning something simple and verifying that this is displayed.
Also, prepare code with an explanation of what it is doing to add to the question. You never know, just doing that may clarify to you what is going wrong. If not, post it up and give someone else a chance to help you.
I cannot really directly solve your problem because you surprisingly haven't given code so as far as I know you could be missing a semicolon! (JK)
This is a detailed answer I gave to someone a while ago, very relevant to your situation
But for a table view use this code:
- (UITableViewCell *)tableView:(UITableView *)tableView2 cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:#"UITableViewCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCell"]
autorelease];
cell.textLabel.text = nil;
}
if (cell) {
//customization
cell.textLabel.text = #"Text Label";
}
return cell;
}
You say you are starting coding.... let me explain. First of try picking up the book:
The Big Nerd Ranch Guide
I suggest you read a heck load of books to get you juiced up on language and API and programming skills
Dequeuing is basically nilling and cacheing any cell that is not visible, aka you scroll past it. Therefore, cell == nil will be called in probably four situations (that I can think of):
When we first setup the table view (cells will be nil)
When we reload data
Whenever we may arrive at this class
When the cells becomes invisible from the table view
So, the identifier for dequeuing is like an ID. Then in the statement to see if cell is nil, we initialize cell, you can see the overridden init method: initWithStyle. This is just what type of cell there is, there are different types with different variables you can customize. I showed you the default. Then we use the reuseIdentifier which was the dequeuing identifier we said earlier. THEY MUST MATCH! I nil textLabel just for better structure, in this case each cell has the same text so it won't matter really. It makes it so the cell that dequeues comes back with the right customization you implemented. Then once cell is actually valid, we can customize.
Also, you are using the same text for each cell. If you do want to have different text for each cell, familiar yourself with NSArray. Then you could provide the array count in numberOfRowsForSection and then do something like this:
cell.textLabel.text = [array objectAtIndex: [indexPath row]];
Where indexPath is the NSIndexPath argument provided in the cellForRowAtIndexPath method. The row variable is the row number, so everything fits!
Wow, that was a lot to take in right! Now go stop being an objective-c noob and start reading some books!
For more info read:
Table View Apple Documentation

Resources