I am trying to set the label that is within a table view cell. I created an array of all the timers that I need in each cell. When I go to construct the cell and hook up the timer label it gives me the unrecognized selector sent to instance error. I have tried everything and can not figure out why I keep getting this. The updateTimerLabel method just returns the timerLabel value so I do not see what the issue is. Can someone help me find out how to fix this?
the error is on the timerLabel
the timersArray holds an array of all created timers.
Here is the cellForRowAtIndexPath method:
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TimerViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"TimerViewCell"];
[[NSBundle mainBundle] loadNibNamed:#"TimerViewCell" owner:self options:nil];
tempCell = [timersArray objectAtIndex:indexPath.row];
cell = tempCell;
tempCell = nil;
cell.timerLabel.text = [cell updateTimerLabel];
return cell;
}
here is the TimerViewCell.h
#import <UIKit/UIKit.h>
#import "Timer.h"
#interface TimerViewCell : UITableViewCell
{
IBOutlet UILabel* timerLabel;
Timer* _timer;
}
#property (strong, nonatomic) IBOutlet UILabel* timerLabel;
- (id)initCellWithTimer:(Timer*)timer;
- (IBAction)startTimerButton;
- (NSString*)updateTimerLabel;
#end
Your need to allocate cell as follows
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TimerViewCell* tempCell = [tableView dequeueReusableCellWithIdentifier:#"TimerViewCell"];
if(tempCell == nil){
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"TimerViewCell" owner:self options:nil];
tempCell = [objects firstObject];
}
tempCell.timerLabel.text = [cell updateTimerLabel];
return tempCell;
}
since you are loading cell from xib. You check your outlet connection of your timerLabel.
and also check is your cell pointing to TimerViewCell? To check click on cell and check is class like image
Here's what's happening in the following three lines of your code:
tempCell = [timersArray objectAtIndex:indexPath.row];
cell = tempCell;
tempCell = nil;
You've assigned an element from timersArray to your class variable tempCell
Next you've pointed your TimerViewCell instance cell to this element.
Finally you remove the pointer that tempCell was holding by assigning it to nil, and since cell is pointed at tempCell, then you are also pointing cell at nil
Then in the next line, you attempt to call updateTimerLabel on nil
cell.timerLabel.text = [cell updateTimerLabel];
I think you'll have more luck with something like this:
- (void)viewDidLoad
{
[self.tableView registerNib:[UINib nibWithNibName:#"TimerViewCell" bundle:nil] forCellReuseIdentifier:#"TimerViewCell"];
}
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TimerViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"TimerViewCell"];
cell.timerLabel.text = [cell updateTimerLabel];
return cell;
}
Well unlike java in ObjC pointers (direct memory locatioin reference) existing and anything you declare with *_instance style will be a pointer.
So when we are assigning one pointer to another they just refer to same location and changing pointer reference to a difreent location (null here) will aparently clean other as well.
tempCell = [timersArray objectAtIndex:indexPath.row];
cell = tempCell;
tempCell = nil;
after these three lines even cell will be pointing to a nil. and hence returning nil to cell which is causing crash.
So removing the line tempCell = nil; should solve the issue.
Related
I want to change the selected cell data permanently as i have done in my didSelectRowAtIndexPath method but the problem is that when I select a row the cell data is change but when i select any other row the previous become as it was, and I also want to save rows in an array, those been selected in an array. here is my code right now.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
#try {
static NSString *cellidentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellidentifier];
if(cell == nil)
{
NSArray *cellObjects = [[NSBundle mainBundle] loadNibNamed:#"Cell" owner:self options:nil];
cell = (UITableViewCell*) [cellObjects objectAtIndex:0];
}
UILabel *label;
long row = [indexPath row];
label = (UILabel *)[cell viewWithTag:10];
label.text =time[row];
label.textColor = [UIColor blackColor];
cell.imageView.image = [img_clock_blue objectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor yellowColor];
[tableView setSeparatorInset:UIEdgeInsetsZero];
//int hecValue;
return cell;
}
#catch (NSException *exception)
{
NSLog(#"%#",exception);
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadData];
UITableViewCell *cell1 = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
cell1.imageView.image = [UIImage imageNamed:#"1_red.png"];
cell1.backgroundColor = [UIColor clearColor];
}
You're modifying the cell, which is a bad idea. You need to modify the place where it's getting its data.
in your didSelectRowAtIndexPathjust find the objectAtIndex:in the array, modify it to your will, then reload the table.
If you only have, for example, titles (NSStrings), then an array of strings will suffice. But most of the time it won't, because you're displaying something custom.
it looks like you don't have a custom class here, so I'll just make an example that you can translate easily. Let's say you're tryign to display a list of Animal objects.
Create your Animal class inheriting from NSObject. (New file, class, and so on).
Add the properties you will need in the Animal.h file, for example
#property (weak, nonatomic) NSString *name;
#property (nonatomic) int size;
#property (nonatomic) int weight;
#property (weak, nonatomic) NSString *countryOfOrigin;
You'll also technically need a class to create/manage/fetch/save these Animal objects but let's keep it simple and do it in the viewDidLoad of your controller.
- (void)viewDidLoad{
[super viewDidLoad];
Animal *myAnimal = [[Animal alloc]init];
myAnimal.name = #"Lion";
myAnimal.size = 13;
myAnimal.weight = 100;
myAnimal.countryOfOrigin = #"NoIdeaHahahah";
// You can hardcode a couple like that, and add them to your array used for your tableview data. Basically we just want some of your custom objects in an array, for your tableview.
}
Ok so now we have an array of Animal (our data) for your tableview. You can use that to create your rows.
When creating the cell in the cellForRow, simply start with :
Animal *animal = [myArray objectAtIndex:indexPath.row];
and then feed your cells with the properties of that animal
cell.titleLabel.text = animal.name;
for example.
And in the didSelect you can modify that specific animal, like I said at the very beginning of this answer :)
Animal *animal = [myArray objectAtIndex:indexPath.row];
animal.name = #"IJustChangedTheName";
[self.tableView reloadData];
All this is common practice, except what we did in the viewDidLoad that is very brutal, but I'm sure you'll be able to adapt that to your code :)
Try this,
create a NSMutableArray #property in view controller. lets say selectedIndexArray
initialize the array in viewDidLoad by self.selectedIndexArray = [[NSMutableArray alloc] init];
in cellForRowAtIndexPath method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//other codes
if ([self.selectedIndexArray containsObject:indexPath]) {
cell.imageView.image = [UIImage imageNamed:#"1_red.png"]; //assumes all selected cells have same image
} else {
cell.imageView.image = [img_clock_blue objectAtIndex:indexPath.row];
}
.....//other code
}
in didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.selectedIndexArray addObject:indexPath];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
The code for setting up cell contents should all be in cellForRowAtIndexPath:.
You should create a real data model to represent the contents of your cells instead of the time array. Create an array of custom objects (or dictionaries) with properties such as "time" and "selected". Use indexPath.row to find the correct object and then use its "selected" property to decide which kind of image to give it.
didSelectRowAtIndexPath: sets "selected" YES or NO and doesn't need to change the cell at all.
I have created a custom UITableViewCell with a UISwitch, a UIStepper and two labels inside.
When I run my app in the simulator, and the tableview lists each instance of this custom cell. I notice that when I toggle the switch in the first cell and increase it's stepper (affecting one label), the ninth cell is affected the same way.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *items = [self arrayForSection:indexPath.section];
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if(indexPath.section == 0){
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.notificationTitle.text = [items objectAtIndex:indexPath.row];
return cell;
}
I also have two sections in this tableview, and set the first one so that the selection style is off.
What is going on exactly and how to do I keep it from happening?
Where is the part where you are creating the custom cell? Are you doing that or is it just that you've missed it out while pasting it here?
Try this (hope you're using a NIB file to create a custom cell):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *questionTableIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:questionTableIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.notificationTitle.text = [items objectAtIndex:indexPath.row];
return cell;
}
When you use this [tableView dequeueReusableCellWithIdentifier:questionTableIdentifier]; you are actually reusing a already made instance of your cell(if there is any to be reused else create a new one). UITableViews work this way in order to conserve memory. If you have a very large number of cells it will still only consume about the same amount of memory as if there were only enough to cover the screen. In order to fix your problem you need to keep the state of your cell's some other place then the cell itself. Maybe a data structure in your tableviewcontroller or viewcontroller. And then set the values when your tableview wants to display the cell.
If you go with the non reusable cells then you could do something like this.
#property(nonatomic, strong)NSArray *cells;
- (id)init
{
self = [super init];
if ( self )
{
_cells = #[#[[[YourCell alloc] init],
[[YourCell alloc] init],
[[YourCell alloc] init]
],
[#[[YourCell alloc] init],
[[YourCell alloc] init],
[[YourCell alloc] init]]];
}
return self;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return _cells[indexPath.section][indexPath.row];
}
Assuming you have 2 sections with 3 cells in each section.
I saw there are lots of resources available on net regarding this question. I have to load a different XIB (UIViewContoller) file for my cell. I have designed my cell looks there and I want to load that design in my cell.
I wrote this code but I am getting an exception.
-[UIView setTableViewStyle:]: unrecognized selector sent to instance 0x6040de0
2011-07-11 14:42:27.461 TableViewTest[2776:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView setTableViewStyle:]: unrecognized selector sent to instance 0x6040de0'
And here is my code of loading nib file
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// Load the top-level objects from the custom cell XIB.
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"loadXibfile" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = [topLevelObjects objectAtIndex:0];
}
return cell;
You should use UITableViewCell and not UIView in your XIB file.
Here is the solution and it is useful for me and home for you or some else may be.
cell = [[[YourCustomcell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"] autorelease];
NSArray *toplavelobject=[[NSBundle mainBundle]loadNibNamed:#"YourCustomcell" owner:self options:nil];
for(id c in toplavelobject)
{
if ([c isKindOfClass:[UITableViewCell class]])
{
cell=(YourCustomcell *) c;
break;
}
}
Though not a direct answer to your issue, I recommend you make use of this UITableViewCellFactory class, it's very convenient:
http://blog.carbonfive.com/2009/07/16/loading-uitableviewcells-from-a-nib-file/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell%d",indexPath.row];
LatestNewsCell *cell = (LatestNewsCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[NSBundle mainBundle] loadNibNamed:#"LatestNewsCell" owner:self options:nil] objectAtIndex:0];
}
}
This is what I did. Again some of the technique is quite awkward and I need improvement.
First I created a new subclass of UITableViewCell. The problem is I do not get an option to check "include" xib. It's as if xib is meant only for UIViewcontroller. I suppose you can create a subclass of UIViewController with XIB and then crate another subclass of UITableViewCell and move the template to your subclass of UIViewController.
Works.
Then I put these function:
#implementation BGCRBusinessForDisplay2
- (NSString *) reuseIdentifier {
return [[self class] reuseIdentifier];
};
+ (NSString *) reuseIdentifier {
return NSStringFromClass([self class]);
};
To initialize I do:
- (BGCRBusinessForDisplay2 *) initWithBiz: (Business *) biz
{
if (self.biz == nil) //First time set up
{
self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
NSString * className = NSStringFromClass([self class]);
PO (className);
[[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
[self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
}
if (biz==nil)
{
return self; //Useful if we only want to know the height of the cell
}
self.biz = biz;
self.Title.text = biz.Title; //Let's set this one thing first
self.Address.text=biz.ShortenedAddress;
The [self addSubview:self.view]; is kind of awkward. It's what other says I should do and it won't work without it. Actually I want self.view to be self, rather than a subView of self. But hei.... Don't know how to do it otherwise.
...
Then I implement this for cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//[FetchClass singleton].FetchController
if([BGMDCRFetchClass singleton].FetchController.fetchedObjects.count!=0){
BGCRBusinessForDisplay2 *cell = (BGCRBusinessForDisplay2*)[tableView dequeueReusableCellWithIdentifier:[BGCRBusinessForDisplay2 reuseIdentifier]];
if (cell == nil)
{
cell =[BGCRBusinessForDisplay2 alloc];
}
else{
while (false);
}
Business * theBiz=[[BGMDCRFetchClass singleton].FetchController objectAtIndexPath:indexPath];
cell = [cell initWithBiz:theBiz];
return cell;
//return theBiz.CustomCell;
}else{
UITableViewCell * tvc=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"tvc"];
return tvc;
}
}
Notice that I separate alloc from init. That's kind of awkward. That is why in my - (BGCRBusinessForDisplay2 *) initWithBiz: (Business *) biz if a cell has been initialized before, I simply don't do the upper part of the init. I simply assign values of Business * to the various outlet in the BGCRBusinessForDisplay2.
I someone can improve my answers they are welcome. So far it works.
I need to create my own UITableViewCell using the xib file, to draw the graphic interface...
what are the right steps to create my new class and to use into my UITableView?
thanks in advance
In iOS5 you'll want to use the new:
registerNib:forCellReuseIdentifier:
Which basically does the same thing...
Personally I think that both suggested tutorials have a big flaw when it comes to reuseIdentifier. If you forget to assign it in interface builder or misspell it, you will load the nib each and every time cellForRowAtIndexPath gets called.
Jeff LaMarche writes about this and how to fix it in this blog post. Apart from reuseIdentifier he uses the same approach as in the apple documentation on Loading Custom Table-View Cells From Nib Files.
After having read all these articles I came up with following code:
Edit: If you are targeting iOS 5.0 and higher you'll want to stick with Duane Fields' answer
#interface CustomCellWithXib : UITableViewCell
+ (NSString *)reuseIdentifier;
- (id)initWithOwner:(id)owner;
#end
#implementation CustomCellWithXib
+ (UINib*)nib
{
// singleton implementation to get a UINib object
static dispatch_once_t pred = 0;
__strong static UINib* _sharedNibObject = nil;
dispatch_once(&pred, ^{
_sharedNibObject = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:nil];
});
return _sharedNibObject;
}
- (NSString *)reuseIdentifier
{
return [[self class] reuseIdentifier];
}
+ (NSString *)reuseIdentifier
{
// return any identifier you like, in this case the class name
return NSStringFromClass([self class]);
}
- (id)initWithOwner:(id)owner
{
return [[[[self class] nib] instantiateWithOwner:owner options:nil] objectAtIndex:0];
}
#end
UINib (available in iOS 4.0 and later) is used here as a singleton, because although the reuseIdentifier is used, the cell still gets re-initialized about 10 times or so. Now cellForRowAtIndexPath looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCellWithXib *cell = [tableView dequeueReusableCellWithIdentifier:[CustomCellWithXib reuseIdentifier]];
if (cell == nil) {
cell = [[CustomCellWithXib alloc] initWithOwner:self];
}
// do additional cell configuration
return cell;
}
A video tutorial showing how to do this with Xcode 4.2 has been made. The author has written a blog post as well.
This tutorial takes you through the whole modern iOS 5 solution, from the process of creating the cell's xib and class files all the way to the finish:
http://mrmaksimize.com/ios/Custom-UITableViewCell-With-NIB/
`You can create custom cells in table view with the help of .xib file. First setup the table view in your view controller, create a new xib file with its class and use it in table view.
- (IBAction)moveToSubCategory:(id)sender;
#property (strong, nonatomic) IBOutlet UILabel *foodCategoryLabel;
#property (strong, nonatomic) IBOutlet UIImageView *cellBg;
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [foodCatArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"ExampleCell";
ExampleCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"ExampleCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
[cell setTag:indexPath.row];
cell.cellBg.image=[UIImage imageNamed:[photoArray objectAtIndex:indexPath.row]];
cell.foodCategoryLabel.text=[foodCatArray objectAtIndex:indexPath.row];
return cell;
}
You can create CustomCell class with XIB that is inherited from UITableViewCell. We will just add category in tableview class .m file in following way. I think this is the easiest method which an be applied for custom cell creation.
#interface UITableViewCell(NIB)
#property(nonatomic,readwrite,copy) NSString *reuseIdentifier;
#end
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 30;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier=#"cell";
CustomCell *cell=[tableView dequeueReusableCellWithIdentifier:identifier];
if(cell==nil)
{
NSLog(#"New Cell");
NSArray *nib=[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell=[nib objectAtIndex:0];
cell.reuseIdentifier=identifier;
}else{
NSLog(#"Reuse Cell");
}
cell.lbltitle.text=[NSString stringWithFormat:#"Level %d",indexPath.row];
id num=[_arrslidervalues objectAtIndex:indexPath.row];
cell.slider.value=[num floatValue];
return cell;
}
#end
Does anyone have suggestions on how to purge a cached UITableViewCell?
I'd like to cache these cells with reuseIdentifier. However, there are times when I need to delete or modify some of the table rows. I expect to call reloadData after the row changes.
Right now, dequeueReusableCellWithIdentifier always returns the cached(obsolete) entry from before. How do I indicate that the cache is stale and needs to be purged?
I'm not sure why you're trying to purge cells in the first place. Every time you dequeue a cell, you need to re-set any data that needs to be displayed. The caching just prevents you from having to set up any non-changing properties every time. But the actual data that's being displayed must be set, even if the cell was cached before.
Note that your reuse identifier is supposed to be the same for all the cells of the same type. If you're doing something silly like calculating the identifier based on the row in question, then you're doing it wrong.
Your code should look something like
- (UITableViewCell *)tableView:(UITableView *)view cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
// no cached cell, create a new one here
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
// set any properties that all such cells should share, such as accessory, or text color
}
// set data for this particular cell
cell.textLabel.text = #"foo";
return cell;
}
In that example, note how I always set the data for the cell every single time, and all cells share the same identifier. If you follow this pattern, you should have no reason at all to try and "purge" your cells, as any old data will be overwritten anyway. If you have multiple types of cells, you may want to use multiple identifiers in that case, but it's still 1 identifier per cell type.
I don't know how to purge the cache, however, I use a workaround how to handle the situation, when the rows need to be changed.
First of all, I don't use static cell identifier. I ask the data object to generate it (pay attention to [myObject signature], it provides a unique string describing all needed properties):
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyObject *myObject = _dataSource[indexPath.row];
NSString *cellId = [myObject signature];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
[myObject configureCell:cell];
}
[myObject updateCellWithData:cell];
return cell;
}
[myObject signature] provides different signatures for different list of properties. So, if my object is changed, I just call [self.tableView reloadData], myObject will provide a new signature, and the table load a new cell for it.
[myObject configureCell:cell] places all needed subviews to the cell.
[myObject updateCellWithData:cell] updates the subviews of the cell with current data.
while ([tableView dequeueReusableCellWithIdentifier:#"reuseid"]) {}
I have found a really good method.
In .h
//add this
int reloadCells;
In .m
- (void)dumpCache {
reloadCells = 0;
[tableView reloadData];
}
-(void)tableView:(UITableView *)tableView cellForRowAtUndexPath:(NSIndexPath *)indexPath {
static NSString *CellID = #"Cell"
UITableViewCell *cell = [tableView dequeCellWithReuseIdentifier:CellID];
if (cell == nil || reloadCells < 12) {
cell = [[UITableViewCell alloc] initWithFormat:UITableViewCellStyleDefault reuseIdentifier:CellID];
reloadCells ++;
}
cell.textLabel.text = #"My Cell";
return cell;
}
I had to something similar today. I have a gallery of images, and when the user deletes them all, the gallery needs to get rid of the last queued cell and replace it with a "gallery empty" image. When the delete routine completes, it sets a 'purge' flag to YES, then does a [tableView reloadData]. The code in cellForRowAtIndexPath looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifier];
if (purge) {
cell = nil;
purge = NO;
}
if (cell == nil) {
//*** Do usual cell stuff
}
return cell
}
I am coding ObjC (manual reference counting) and found some strange behavior that prevents UITableView and UITableViewCells from getting released, although my memory management is correct. UITableView and UITableViewCell seem to retain each other. And yes, I found a way to force the UITableView and its cells to release each other (remove cached cells).
To do this, basically you ask a table view for a reusable cell. The table view will remove it from its reusable cells cache. After all you tell that cell to remove from its superview. Done.
Now as a stand-alone class... First, you need an array of all cell identifiers you used. Next, you implement a dummy data source and let that data source clear the UITableView by reloading itself with the "ClearAllCachedCells" Data source:
#import <UIKit/UIKit.h>
#interface ClearAllCachedUITableViewCellsDataSource : NSObject <UITableViewDataSource, UITableViewDelegate>
+(void)clearTableView:(UITableView*)tv
reuseIdentifiers:(NSArray<NSString*>*)cellIdentifiers
delegateAfterFinish:(id<UITableViewDelegate>)dg
dataSourceAfterFinish:(id<UITableViewDataSource>)ds;
#end
And the magic happens in the .m file:
#import "ClearAllCachedUITableViewCellsDataSource.h"
#interface ClearAllCachedUITableViewCellsDataSource () {
BOOL clearing;
}
#property (nonatomic, readonly) UITableView *tv;
#property (nonatomic, readonly) NSArray<NSString*> *identifiers;
#property (nonatomic, readonly) id ds;
#property (nonatomic, readonly) id dg;
#end
#implementation ClearAllCachedUITableViewCellsDataSource
-(void)dealloc {
NSLog(#"ClearAllCachedUITableViewCellsDataSource (%i) finished", (int)_tv);
[_tv release];
[_ds release];
[_dg release];
[_identifiers release];
[super dealloc];
}
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
[self performSelectorOnMainThread:#selector(clear) withObject:nil waitUntilDone:NO];
NSLog(#"TV (%i): reloading with zero cells", (int)_tv);
return 0;
}
-(void)clear {
if (!clearing) {
clearing = YES;
for (NSString *ident in self.identifiers) {
UITableViewCell *cell = [_tv dequeueReusableCellWithIdentifier:ident];
while (cell) {
NSLog(#"TV (%i): removing cached cell %#/%i", (int)_tv, ident, (int)cell);
[cell removeFromSuperview];
cell = [_tv dequeueReusableCellWithIdentifier:ident];
}
}
self.tv.delegate = self.tv.delegate == self ? self.dg : self.tv.delegate;
self.tv.dataSource = self.tv.dataSource == self ? self.ds : self.tv.dataSource;
[self release];
}
}
+(void)clearTableView:(UITableView*)tv
reuseIdentifiers:(NSArray<NSString*>*)cellIdentifiers
delegateAfterFinish:(id<UITableViewDelegate>)dg
dataSourceAfterFinish:(id<UITableViewDataSource>)ds {
if (tv && cellIdentifiers) {
NSLog(#"TV (%i): adding request to clear table view", (int)tv);
ClearAllCachedUITableViewCellsDataSource *cds = [ClearAllCachedUITableViewCellsDataSource new];
cds->_identifiers = [cellIdentifiers retain];
cds->_dg = [dg retain];
cds->_ds = [ds retain];
cds->_tv = [tv retain];
cds->clearing = NO;
tv.dataSource = cds;
tv.delegate = cds;
[tv performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
}
#end
The old data from previous usage of the cell should be cleared with the message prepareForReuse. When a cell is dequeued this message is sent to the cell before it is returned from dequeueReusableCellWithIdentifier:.
No way, I think...
I used a completely different approach. Instead of relying on UITableView cache, I build my own. In this way I have perfect control on when and what to purge.
Something like this...
given:
NSMutableDictionary *_reusableCells;
_reusableCells = [NSMutableDictionary dictionary];
I can do:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"whatever-you-need";
UITableViewCell *cell = [_reusableCells objectForKey:cellIdentifier];
if (cell == nil)
{
// create a new cell WITHOUT reuse-identifier !!
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
// store it in my own cache
[_reusableCells setObject:cell forKey:cellIdentifier];
/* ...configure the cell... */
}
return cell;
}
and:
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[_reusableCells removeAllObjects];
}
Hope this may help.