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.
Related
I'm new in CoreData and using MagicalRecord to rule with it. My problem is that I have the UITableView with an NSArray as dataSource populated with objects which fetched from CoreData db, and everything seems fine until I scroll the table for some times.
Here is my code:
Method for fetching data (MyDatabase.m):
+(NSArray *)getEntities{
...
return [MyEntity MR_findAllSortedBy:#"name" ascending:YES withPredicate:predicate];
}
Here is how I fetch and set data to UITableView in my ViewController:
- (void)viewDidLoad {
[super viewDidLoad];
myEntitiesArray = [MyDatabase getEntities];
if(myEntitiesArray.count != 0)
[myTableView setTableData:myEntitiesArray];
}
Here is setTableData method implementation in MyTableView.m:
- (void)setTableData:(NSArray *)array {
if (array && [array count] > 0) {
_tableData = array;
[self reloadData];
}
}
And here is how I set up my cells in MyTableView.m:
- (void)tableView:(UITableView *)tableView willDisplayCell:(SSCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
}
I tried to put an NSLog(#"name is %#",[(MyEntity *)_tableData[indexPath.row] name]) into willDisplayCell and found that when cells become empty, NSLog prints out the messages "name is (null)". I know this question is possibly solved by many people and many times before I faced this problem. Hope someone will help me to solve it too :)
UPDATE: cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"ssCell";
SSCell *cell = (SSCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if( !cell ) {
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell setSelectedBackgroundView:selectedBackgroundView];
}
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
return cell;
}
I also call this method inside MyTableView.m init method:
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:#"ssCell"];
You have to use cellForRowAtIndexPath. In this method the cells are allocated.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/*
* This is an important bit, it asks the table view if it has any available cells
* already created which it is not using (if they are offscreen), so that it can
* reuse them (saving the time of alloc/init/load from xib a new cell ).
* The identifier is there to differentiate between different types of cells
* (you can display different types of cells in the same table view)
*/
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
/*
* If the cell is nil it means no cell was available for reuse and that we should
* create a new one.
*/
if (cell == nil) {
/*
* Actually create a new cell (with an identifier so that it can be dequeued).
*/
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MyIdentifier"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
/*
* Now that we have a cell we can configure it to display the data corresponding to
* this row/section
*/
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
return cell;
}
You should be initializing the cell by calling init. Instead you are doing the following:
if( !cell ) {
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell setSelectedBackgroundView:selectedBackgroundView];
The second call attempts to again reuse an existing cell when there isn't one available. That would probably return nil again.
be very careful of the "feature" of objective C, where calling a method of a nil object does nothing. Instead of crashing with null.pointer.exception like Java, it probably floats over [cell setSelectedBackgroundView:selectedBackgroundView] and a whole bunch of other lines without a problem.
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.
So I've used this tutorial to populate a UITableView with custom cells that represent balances. When stepping through the code, I witness the correct amount of cells get created (only 4 with the current test data) and their labels' text set correspondingly.
My problem is when the table is displayed on the screen, only the first row/cell is displayed.
Any insight as to why this could be occurring would be greatly appreciated!
Removed old code.
BalanceCell.h:
#import <UIKit/UIKit.h>
#interface BalanceCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#property (weak, nonatomic) IBOutlet UILabel *amountLabel;
#property (weak, nonatomic) IBOutlet UILabel *modifiedLabel;
#end
EDIT:
My TableView delegate methods are now as follows:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [_balances count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
BalanceCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[BalanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [_hex colorWithHexString:_themeColourString];
return cell;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(BalanceCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
Balance *item = [_balances objectAtIndex:indexPath.row];
cell.nameLabel.textColor = _themeColour;
cell.nameLabel.text = item.name;
cell.amountLabel.textColor = _themeColour;
cell.amountLabel.text = [NSString stringWithFormat:#"%#%#", item.symbol, item.value];
cell.modifiedLabel.textColor = _themeColour;
cell.modifiedLabel.text = [NSString stringWithFormat:#"%#", item.modified];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 94;
}
As #Sebyddd suggested, I now register the NIB in the viewDidLoad method.
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"BalanceCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
}
These changes may make my code more correct but still only the first cell is displayed.
If cells are getting created and returned properly I guess height is not being set propery. By default I beleive all cells have a height of 44. If your cell exceeds this height it might not get displayed.
You can tell the tableview to adjust height for every cell using (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath delegate
In that delegate just return your cells height.
EDIT:
You are using dequeueReusableCellWithIdentifier: which will return A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.
Instead use dequeueReusableCellWithIdentifier:forIndexPath: which will return A UITableViewCell object with the associated reuse identifier. This method always returns a valid cell.
You need to register the nib/class for that custom cell in viewDidLoad
Try this:
if (cell == nil) {
[tableView registerNib:[UINib nibWithNibName:#"BalanceCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [[BalanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
Use this tuto : http://www.appcoda.com/uitableview-tutorial-storyboard-xcode5/ , your tuto is a bit outdated, and hard to follow !
I'm seeking how create several cells to go to different ViewControllers.
For my TableView, I'm using a subclass of UITableViewController.
And when I choose 2 in the following method, I just see 2 identical cells which are doing exactly the same thing. I'm not interested by this. I don't even know their IndexPath in order to change their title.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 2;
}
And When I try to put another UITableViewCell in my TableView, it doesn't appear on iOS simulator, even with the same option (same subclass) than my first UITableViewCell which I can see.
Thanks for your help.
Edit : Here is my new code to create 2 cells but doesn't work :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell2";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[customCell alloc] init];
}
static NSString *CellIdentifier1 = #"Cell1";
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if (cell1 == nil) {
cell1 = [[customCell alloc] init];
}
// Configure the cell...
return cell;
}
You define your cells in tableView:cellForRowAtIndexPath:, so you should provide an implementation for that method.
tableView:numberOfRowsInSection: only returns the number of cells in the table.
If you need more help, please provide your implementation for tableView:cellForRowAtIndexPath:. This is how a typical implementation looks like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
... customize your cell ...
}
EDIT:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell2";
static NSString *CellIdentifier1 = #"Cell1";
if(indexPath.row == 0 ) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[customCell alloc] init];
}
} else {
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if (cell1 == nil) {
cell1 = [[customCell alloc] init];
}
}
return cell;
}
This method gets called when a cell has been selected. You can decide what you wanna do according to the selected row
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0)
[self goToFirstViewController];
else
if(indexPath.row == 1)
[self goToSecondViewController];
}
Use the following:
- (NSInteger) tableView: (UITableView*) tableView numberOfRowsInSection: (NSInteger) section
This delegate method returns the number of rows you want in that particular section. So if you want more than 2 rows, or you want the number of rows to be dynamic, you can create a NSArray in the AppDelegate or in the init method of the viewController class, and return the number in the numberOfRowsInSection method like
return [delegate numberOfNames];
In my example above, I created an array in my AppDelegate and also a method to return the number of objects I have in that array so that I can create the number of rows for my table.
- (UITableViewCell*) tableView: (UITableView*) tableView cellForRowAtIndexPath: (NSIndexPath*) indexPath
This delegate method will show what you want to display in each cell. Therefore, following on from my array created in my AppDelegate, I first create the cell, then I will set the text I want to display on the cell with a method I created in my AppDelegate that will return a NSString while taking in a NSInteger so that I can loop through my array and display the text accordingly.
static NSString* MyIdentifier = #"Default";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if( cell == nil )
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
cell.textLabel.text = [delegate nameAtIndex:indexPath.row];
}
nameAtIndex is the name of the method I created in my AppDelegate that will return the NSString object at the specific index (ie. the row number) from the NSArray I created to store all the items of my table.
When the user clicks on any of the rows in the table created, this delegate method will be called
- (void) tableView: (UITableView*) tableView didSelectRowAtIndexPath: (NSIndexPath*) indexPath
And in here, I will check if the text displayed matches any of the items in my array from the AppDelegate that stores the items in the table, and create the view that is necessary.
UIViewController* viewController = nil ;
NSString* nameInArray = [delegate nameAtIndex:indexPath.row] ;
if( [nameInArray isEqualToString:#"firstName"] )
{
viewController = [[FirstViewController alloc] init];
}
else if( [nameInArray isEqualToString:#"secondName"] )
{
viewController = [[SecondViewController alloc] init];
}
else if( [nameInArray isEqualToString:#"thirdName"] )
{
viewController = [[ThirdViewController alloc] init];
}
So with these 3 delegate methods, you will be able to create the table using a NSArray created, and be able to redirect the user to a viewController according to which option in the table he chooses. You will not have to keep editing the delegate methods if you choose to add more rows to the table as well since you are returning the count of the array when setting up the table.
The array and methods to get the data of the array can be created in the viewController as well, not necessarily in the AppDelegate, in case you were wondering.
The methods are as follows:
-(NSInteger) numberOfNames
{
return [myArray count];
}
-(NSString*) nameAtIndex: (NSInteger) index
{
return [myArray objectAtIndex:index] ;
}
Hope this helps! :)
I am beginning with iOS have a silly problem. I just want to show a TableView populated with strings that are stored in a NSMutableArray. I can see that the strings are in the array, but for some reason the TableView is not showing them.
I have bascially this:
#interface Test ()
#property (weak, nonatomic) IBOutlet UITableView *contactList;
#property (strong, nonatomic) NSMutableArray *contactsArray;
#end
- (void)onContactFound:(NSString*)contact
{
[self.contactsArray addObject:contact];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.contactsArray count];
}
//4
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//5
static NSString *cellIdentifier = #"SettingsCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//6
NSString *tweet = [self.contactsArray objectAtIndex:indexPath.row];
//7
[cell.textLabel setText:tweet];
[cell.detailTextLabel setText:#"via Codigator"];
return cell;
}
I think the problem is in the last part. I copied this code from an example (http://www.codigator.com/tutorials/ios-uitableview-tutorial-for-beginners-part-1/) that said I should add some dynamic properties but in my TableView I do not have these properties in the attributes inspector so basically I do not have the #"SettingsCell" so I guess this is one of the problems at least, maybe this code does not apply in my case and it should be done in another way?
I think you are trying to dequeue a cell without ever creating cells. I think you only get nil cells back. You should use something like this:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
Also have a look in the API documentation which states:
dequeueReusableCellWithIdentifier: Returns a reusable table-view cell object located by its identifier.
Return Value: A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.
dequeueReusableCellWithIdentifier: