Disable cell reuse for small fixed-size UITableView - iphone

I have a small, fixed-size table, and I want to load UITableView entirely into memory, and never reuse cells if they scroll out of view. How do I achieve this?
I'm not using a UITableViewController; just a simple UIViewController that implements the proper protocols (UITableViewDataSource and UITableViewDelegate).

Set nil for reuse identifier in the line
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
Or just remove the line and add,
UITableViewCell *cell = nil;

Just do not implement the method UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SomeID"]; and none of your cells will be reused. Each time it ask for a cell you create a new one and configure it.

You should pass nil in the method initWithStyle:reuseIdentifier: if you don't want to reuse cells but keep in mind the performance. As long as it is good, you should be ok passing nil.

First three answers are completely correct, you just need to make sure that you do not call dequeueReusableCellWithIdentifier function on a UITableView. In addition to this, you can simply allocate the cells in code. First you will need an instance variable that will store the pointers to your cells (I will assume you use a normal view controller):
#interface MyViewController
#property (nonatomic, strong) NSArray* myTableViewCells;
#end
Then you can lazily instantiate this array, by overriding it's getter:
- (NSArray *)myTableViewCells
{
if (!_myTableViewCells)
{
_myTableViewCells = #[
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]
];
}
return _myTableViewCells;
}
Add more cells into array if you like, or use NSMutableArray. Now all you have to do is to wire up this array to proper UITableViewDataSource methods.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.myTableViewCells.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = self.myTableViewCells[indexPath.row];
//
// Add your own modifications
//
return cell;
}
This makes for a much cleaner code, less prone to memory leaks (static variables get deallocated when program ends, so why are we keeping table view cells in memory if the view controller that is displaying them is already gone?).
The addition of new cells is also much easier (no switch or if statements required) and code is more nicely structured.

EDITED
Sometimes you need some cells to be static, for example, you need the first cell to be downloading cell which has download progress bar. and other cells to be waiting for download cells. In this case, the first cell should be accessible for pause and resume functions(outside tableView:cellForRowAtIndexPath:).
you could try to create static cells like this:
1st: subclass UITableViewCell, to create your own cell (this is a option)
2nd: crate static cell in your view controller
static YourSubclassedTableViewCell *yourCell_0;
static YourSubclassedTableViewCell *yourCell_1;
static YourSubclassedTableViewCell *yourCell_2;
static YourSubclassedTableViewCell *yourCell_3;
3rd: Init cells in viewDidLoad (viewDidLoad is a good choice to put init code)
- (void)viewDidLoad
{
yourCell_0 = [[YourSubclassedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
yourCell_1 = [[YourSubclassedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
yourCell_2 = [[YourSubclassedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
yourCell_3 = [[YourSubclassedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
// or simply
yourCell_0 = [[YourSubclassedTableViewCell alloc] init];
yourCell_1 = [[YourSubclassedTableViewCell alloc] init];
yourCell_2 = [[YourSubclassedTableViewCell alloc] init];
yourCell_3 = [[YourSubclassedTableViewCell alloc] init];
}
4th: Load cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0:
yourCell_0.textLabel.text = #"1st Row";
return yourCell_0;
case 1:
yourCell_1.textLabel.text = #"2nd Row";
return yourCell_1;
case 2:
yourCell_2.textLabel.text = #"3rd Row";
return yourCell_2;
case 3:
yourCell_3.textLabel.text = #"4th Row";
return yourCell_3;
default:
defaultCell....(ignore)
return defaultCell;
}
}
**As described above, cells are created once and can be accessed outside tableView:cellForRowAtIndexPath:
You also could declare cells as #property to make it accessible for other class.

Just alloc a new cell in place of dequeue no need to do any of the above. Performance implications are negligible for small tableView (< 100 cells).
Example in swift 3
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier:"Cell")
return cell
}
Cheers

Related

How can I store my UITableViewCells in a NSMutableArray?

Basically I'm making a list view that you can add things to the top of. The best way I can think of doing this is to store the UITableViewCells themselves in a NSMutableArray — Because I can simply pull them from the array them with all their data inside the object, and this list view will never be over 10 cells long.
Also note that I'm using Storyboards, hence the initWithCoder use.
The following code is what I'm trying, and it doesn't work:
// This is where my NSMutableArray is initialized:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
if (!_CellsArray) {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TestCell"];
_CellsArray = [NSMutableArray arrayWithObject:cell];
}
}
return self;
}
//UITableView Delegate & DataSource Methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TestCell"];
[_CellsArray insertObject:cell atIndex:0];
return [_CellsArray objectAtIndex:indexPath.row];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
I realize I may be approaching this in the wrong way, that's why I'm here though :)
Thank you.
edit: fixed a type in the code (TimerCell -> UITableViewCell)
Let's look at the order things get called in and what happens.
Your view controller is unarchived, so your initWithCoder: method is called. This method creates a mutable array and puts one instance of TimerCell into it. Said instance is not further configured (unless you've overridden initWithStyle:reuseIdentifier: to do some configuration).
Your data source method tableView:numberOfRowsInSection: is called, and it tells the table view there are ten rows.
Thus, your tableView:cellForRowAtIndexPath: is called ten times. Each time, it creates a new instance of UITableViewCell and inserts it into your mutable array. (After ten calls, your mutable array contains one TimerCell at index 10 and ten UITableViewCells at indices 0-9.) It does nothing to configure the cell's contents or appearance, then it returns the cell at the specified row index. On the first call, you're asked for row 0, so the cell you just created and inserted at index 0 is returned. On the second call, you're asked for row 1, so the cell at index 1 in your array is returned -- since you just inserted a new cell at index 0, the cell you created on the last call has shifted to index 1, and you return it again. This continues with each call: you return the same unconfigured UITableViewCell ten times.
It looks like you're trying to out-think UIKit. This is almost never a good thing. (It's been said that premature optimization is the root of all evil.)
UITableView already has a mechanism for cell reuse; it's best to just keep track of your own cell content and let that mechanism do its thing. I took so long to type this that other answers have been written describing how to do that. Look to them, or to Apple's documentation or any third-party UITableView tutorial.
Why don't you just store the cell information in an array. Then in the -cellForRowAtIndexPath: method, just extract the data needed to change each cell.
Here is a simple example:
//Lets say you have an init like this that inits some cell information
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
cellArray = [NSArray alloc] initWithObjects:#"firstCell",#"secondCell",#"thirdCell",nil];
}
return self;
}
//then for each cell, just extract the information using the indexPath and change the cell that way
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.textLabel.text = [cellArray objectAtIndex:indexPath.row];
return cell;
}
Table views don't store things. Rather, they just ask for the data they want to display, and you typically get that data from elsewhere (like an NSArray, or an NSFetchedResultsController). Just store the things you want into some data container, and let the table display them for you.
// Probably your data model is actually a member of your class, but for purposes of demonstration...
static NSArray* _myArray = [[NSArray alloc] initWithObjects:#"Bob", #"Sally", #"Joe", nil];
- (NSInteger) tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [_myArray count];
}
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString* CellIdentifier = #"TestCell";
// Make a cell.
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if( cell == nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Setup the cell with the right content.
NSString* aString = [_myArray objectAtIndex:[indexPath row]];
cell.textLabel = aString;
return cell;
}
Now if you want more stuff in the list, add it to your array, and you're done.
Edit: On another note, initWithCoder: isn't generally the best place to do initialization for a view controller. Reason being, at the point that it's called, there's a good chance that stuff isn't loaded yet (IBOutlets, for example). I tend to prefer viewDidLoad (don't forget to cleanup in viewDidUnload in that case), or awakeFromNib.

uitableviewcontroller does not show anything

I have storyboard and implement master detail application app ,when i assign manuel class to a tableviewcontroller the objects inside it does not showed and i only get blank table ,please help.
“I defined it as Static Cells and then assign manual class ,I use default code when you add uitbleviewcontroller subclass ,i use the default codes and didn't change the code.”
Well of course it’s blank, you’re not giving the table view any data to work with. You need to implement cellForRowAtIndexPath and configure each cell there.
A sample implementation would be:
-(UITableViewCell *) tableView: (UITableView *) tableView
cellForRowAtIndexPath: (NSIndexPath *) indexPath {
UITableViewCell *cell = [self.table dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"CellIdentifier"];
}
cell.textLabel.text = [self.dataArray objectAtIndex:indexPath.row];
return cell;
}
You should read the table view programming guide.

iPhone:TableView cellForRowAtIndexPath method issue

I am facing a crashing problem in cellForRowAtIndexPath tableview delegate method
#interface EventListView : UIViewController <UITableViewDelegate, UITableViewDataSource>
IBOutlet UITableView *tblView;
NSMutableArray *arr_EventValues,*arr_Event_Details;
NSMutableArray *arr_EventListDetails;
#property(nonatomic, retain)NSMutableArray *arr_EventValues,*arr_EventListDetails, *arr_Event_Details;
#property(nonatomic, retain)UITableView *tblView;
- (void)viewDidLoad
{
appdelegate = (VibesGuideAppDelegate *)[[UIApplication sharedApplication] delegate];
ViewCalendar = [[CalendarView alloc] initWithNibName:#"CalendarView" bundle:[NSBundle mainBundle]];
[self.navigationController.navigationBar setHidden:YES];
self.arr_Event_Details = [[NSMutableArray alloc] init];
self.arr_EventValues = [[NSMutableArray alloc] init];
[super viewDidLoad];
}
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self.arr_EventListDetails count] > 0)
{
return [self.arr_EventListDetails count];
}
return 0;
}
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
// ------------------------------- Custom cell ------------------------------
Customcell *cell = (Customcell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[[Customcell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.text = #"Hello";
return cell;
}
* -[EventListView tableView:cellForRowAtIndexPath:]: message sent to deallocated instance 0x60e01b0 and I used tableview from xib and set all connections and also got arrayvalues in numberOfRowsInSection in method but in cellForRowAtIndexPath method is not called so please give me idea my issue....
Thanks in advance.
At first it seems your some variable gets released. Make sure you have properly allocated it. If you have declared #property for it you better use the variable as self.variable. You can use retain and in case of IOS 5 strong in property declaration.
Just to be sure you can track if any variable gets released via setting NSZombieEnabled to YES. With zombies enabled, messages to deallocated objects will no longer behave strangely or crash in difficult-to-understand ways, but will instead log a message and die in a predictable and debugger-breakpointable way. You can set NSZombieEnabled by the following steps.
Select Product from the menu bar above. Keep alt/option pressed and select "Test..." or "Run...". Go to the Arguments tab, and add NSZombieEnabled YES in the "Environment Variables" section.
Your tableview itself is already released - the error message says that you send the cellForRowAtIndexPath message to a deallocated instance of the table view - so your problem lies somewhere in not retaining or releasing the EventListView and cannot be seen in the code displayed here.
Check this one:
Table DataSource and delegate is set or not.
Used array in cellForRowAtIndexPath is properly set with property and synthesized as well used with self. name.
First of all you dequeue the cell and then create a new one. This is not a good practice, if you are able to dequeue a cell you should not create a new one. You should have something like this:
Customcell *cell = (Customcell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[Customcell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
fix this and let me know if you are still running on the same problem.

UITableView not scrolling smoothly...(iPhone SDK) ..!

UITableView not scrolling smoothly...(iPhone SDK) ..!!
I have implemented UITableView DataSource and Delegate methods in an individual separate classes.(one for delegate and one for datasource) in main program i write only:
//assume that all objects are allocated
ObjTableView.dataSource=ObjDataSource;
ObjTableView.delegate = ObjDelegate;
[self.view addSubView: ObjTableView];
when i run this code , UITable view appears but when i try to scroll it, it doesn't scroll smoothly.
I have also checked that UITableViewCell doesn't redraw once the cell is initialized.
can any one tell me why this happens ? How can i solve this problem ??
From comments:
ListDataSource *ObjListDataSource = [[ListDataSource alloc]initWithArray:[[sender object] valueForKey:#"List"]];
ListDelegate *ObjListDelegate = [[ListDelegate alloc]initWithArray:[[sender object] valueForKey:#"List"]];
tblList = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
tblList.dataSource = ObjListDataSource; tblList.delegate = ObjListDelegate;
[self.view addSubview:tblList]; [tblShopList release];
More from comments:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom cell code
}
return cell;
}
More Information:
I have used NSNotification which notifies to current class when parsing is complete, after receiving notification , current class method calls DataSource, Delegate methods (which is defined in a separate class file).
So UItableViewCell customization (which is in ListDataSource) and table view(in current class) both are in different classes.
A problem is
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
The id needs to be the same for all cells of the same class, otherwise you never reuse them. As you can see in most examples, it is indeed a constant in most (all?) cases.
Little explaination on the reuseIdentifier: every time a cell gets out of screen, you can reuse it instead of creating a new one. To reuse it, you need a cell in queue with the same identifier as the one you pass to dequeueReusableCellWithIdentifier. The way you did, the cells are never reused, because each id is unique (they may or may not be reused in case a row reappears on screen, depending on queue size, which is not configurable AFAIK). This is why personalization of the cell should happen OUTSIDE the "cell == nil" block. Long story short, you are using the reuseIdentifier not as intendend.
I think Michele is correct, but I would also add that it looks like you are doing your cell customization where the cell gets created. What you should be doing is something more like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = (UITableViewCell)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom REUSABLE cell code here, e.g. text color, etc.
}
NSString *cellText = [dataArray objectAtIndex:indexPath.row]; //assuming you have a simple array for your data
cell.textLabel.text = cellText;
return cell;
}
I would also add that I'm not sure why you are able to run the app with the code you have here, since UITableViewCell cell = ... is an invalid initializer. It should be UITableViewCell *cell = ....
It would be helpful to see how you are customizing your cell, since without that it's hard to see what's happening.

Force UITableView to dump all reusable cells

I have a UITableView where I have the backgroud color set via
UIView *myView = [[UIView alloc] init];
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
cell.backgroundView = myView;
[myView release];
The problem I find is that when I edit a table (via setEditing:YES...) some cells of the same color invariable are next to each other. How do I force UITableView to fully redraw. reloadData is not doing a great job.
Is there are deep-cleaning redraw?
I had this issue before so I'll share with you how I solved it:
You can use a boolean flag (say it's called needsRefresh) to control the behavior of cell creation in -cellForRowAtIndexPath:
An example:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell || needsRefresh) {
cell = [[[UITableViewCell alloc] init....] autorelease];
}
//.....
return cell;
}
So, when you need a hard reload, set the needsRefresh flag to YES. Simple as a pimple.
For me the accepted answer didn't really work since I had no idea when to set the needsRefresh back to YES.
What worked for me was:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:customCellIdentifier];
if(nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:customCellIdentifier];
}
//.....
return cell;
}
And then you change the customCellIdentifier value whenever you need to. This way the cells are also still reusable if you switch back to the original cell identifier.
The accepted method seems dirty, it just makes a bunch of new cells that are stored along with the bad ones. Here are a couple of solutions depending on your situation:
1.
first, for the situation described in the question you should not dump your cells and create new views on every cycle. You need to tag your view and then get it back when from the cell when you get a reuse cell:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell) {
cell = [[UITableViewCell alloc] init];
UIView *myView = [[UIView alloc] init];
cell.backgroundView = myView;
[myView setTag:5]; //<------
}
UIView *myView = [cell viewWithTag:5]; //<------
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
return cell;
}
//then just reload the tableview.
2.
...or even better, why not just use the cell backgrouncolor and update that without creating a view.
3.
A sure way to really clear out old cached cells it to simply recreate the UITableView object.
4.
In most cases you dont need to destroy these cells, just keep track of your elements and update them after getting the reusable cell.You can tag all your elements, keep a array reference to them, find them thought the view hierarchy... Im sure theres a bunch of other ways.
5.
heres a one liner to directly purge all cells, although not best practice to mess with the internals of objects like this as they might change in future versions:
[(NSMutableDictionary*)[tableview valueForKey:#"_reusableTableCells" ] removeAllObjects];
I was able to solve this by adding a refresh variable to the table datasource. I used a dictionary for each cell, but there's an extra key called #"refresh":#"1", indicating the cell needs refreshing. Once it's updated, I set that key's value to #"0". So whenever the table is reloaded, make sure the key goes back to #"0" again.
#define TABLE_VIEW_CELL_DEFAULT_ID #"cellIdentifier"
#property (nonatomic, strong) NSString *tableViewCellIdentifier;
#property (nonatomic) NSUInteger tableViewCellIdentifierCount;
// By using a different cell identifier, this effectively flushes the cell
// cache because the old cells will no longer be used.
- (void) flushTableViewCellCache
{
self.tableViewCellIdentifierCount++;
self.tableViewCellIdentifier = [NSString stringWithFormat:#"%#%i", TABLE_VIEW_CELL_DEFAULT_ID, self.tableViewCellIdentifierCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.tableViewCellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.tableViewCellIdentifier];
}
// rest of method...
}