How do I read intValue of an array of NSIndexpaths through indexPathsForVisibleRows?
By the way, why do visibleCells and indexPathsForVisibleRows not work before if (cell == nil) function?
Here is my code:
In the cellForRowAtIndexPath method:
static NSString *identifierString;
UITableViewCell *cell = [tableView1 dequeueReusableCellWithIdentifier:identifierString];
// when I use visibleCells and indexPathsForVisibleRows here, the app crashes
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifierString] autorelease];
cell.accessoryType = UITableViewCellAccessoryNone;
}
// when I use visibleCells and indexPathsForVisibleRows here, the app works
//cell implementation here
return cell;
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath is where the tableview gets populated with cells. If you try to reference visible cells before they have been created, which is done within the if-statement, then the app crashes.
The alloc command allocated memory for the cell to be created and then it gets initialized with certain parameters. This method gets called as many times as you designate in numberOfRowsInSection.
So that you don't recreate all the cells again and again the if-statement checks if the cell does exist and only if it is nil creates a new one to take the place.
To get the int row value of an IndexPath you can use it's row property. For example:
NSArray indexArray = [self.tableView indexPathsForVisibleRows];
int i=0;
while(i!=indexArray.count){
//Log out the int value for the row
NSLog(#"%d", indexArray[i].row);
i++;
}
Hope this helps
Related
I have an NSArray (called masterArray) with 2 different type of objects in it - object1 and object2. These objects require 2 different table view cells (each one made programmatically), I implemented this as such
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[masterArray objectAtIndex:indexPath.row] isKindOfClass:[object1 class]])
{
cell1 *cell = (cell1 *)[tableView dequeueReusableCellWithIdentifier:#"obj1Cell"];
if (cell == nil)
{
cell = [[cell1 alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"obj1Cell"];
}
//set up labels and stuff
return cell;
}
if ([[masterArray objectAtIndex:indexPath.row] isKindOfClass:[object2 class]])
{
cell2 *cell = (cell2 *)[tableView dequeueReusableCellWithIdentifier:#"obj2Cell"];
if (cell == nil)
{
cell = [[cell2 alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"obj2Cell"];
}
//set up labels and stuff
return cell;
}
return nil;
}
Now, this code displays the cells fine. When you tap a cell, I use this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
newViewController *newVC = [[newViewController alloc]init];
newVC.item = [masterArray objectAtIndex:indexPath.row];
[self.navigationController pushViewController:newVC animated:YES];
}
When I tap however, I get the error message "UITableView failed to obtain a cell from its dataSource". I've looked at other questions answering this too. This one says to add the line
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
so I do so, replacing my "cell1 *cell = (cell1 *)..." line with
cell1 *cell = (cell1 *)[tableView dequeueReusableCellWithIdentifier:#"obj1Cell" forIndexPath:indexPath];
yet now I get a
"'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier obj1cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard"
error, which doesn't apply in my situation because my UITableViewCells are made programmatically (there is no storyboard). Also recommended in that post is to add "UITableViewDelegate, UITableViewDataSource", however this appears to only be an option in Swift, not Objective-C.
Before using dequeueReusableCellWithIdentifier with an ID, you need to register this ID like so:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = ...;
[self.tableView registerClass:[cell1 class] forCellReuseIdentifier:#"obj1Cell"];
[self.tableView registerClass:[cell2 class] forCellReuseIdentifier:#"obj2Cell"];
}
Additional notes:
if (cell == nil) is redundant, because it is never nil in practice.
In Objective-C the convention is to have class names started from an app prefix (2-3 uppercase letters), and an uppercase letter, e.g.: MYCell1, MYCell2, MYObject1, etc. (replace MY with something meaningful for you).
It seems to work much faster to do this:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
JHomeViewCell *cell = (JHomeViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[JHomeViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
cell.cellContent.thumbnailCache = self.thumbnailCache;
}
Entry *entry = [self.resultsController objectAtIndexPath:indexPath];
if (cell.cellContent.entry != entry) {
[cell.cellContent setNeedsDisplay];
}
}
The problem is when an entry is edited, the cell doesn't change. I could check for all elements of the cell, to see if they're different, but is there a better way of doing this? Calling drawrect every time the cell appears slows the app down and seems unnecessary.
If you want to do custom drawing for your tableview cell you have to subclass UITableViewCell (as it looks you have done with JHomeViewCell).
Put drawRect inside of your implementation of JHomeViewCell
#implementation JHomeviewCell
- (void)drawRect:(CGRect)rect
{
[super drawRect];
// Insert drawing code here
}
#end
Also, you shouldn't call drawRect directly. You should call setNeedsDisplay instead, and probably need to set the cellContent.entry value to the entry you have from your results controller.
Entry *entry = [self.resultsController objectAtIndexPath:indexPath];
if (cell.cellContent.entry != entry) {
cell.cellContent.entry = entry;
[cell setNeedsDisplay];
}
Recently started developing apps, so excuse my ignorance. I have a tableView, and when a cell in the table view is clicked, I want to insert a new row directly below it. This currently works in my code. However, I also want the row that has been inserted to be removed once the cell has been clicked again. This is giving me the NSRangeException saying I am out of bounds in my array.
I figured this probably has to do with my tableView delegate/data methods, so I set up break points at each of them. With the break points enabled, the cell is removed perfectly. However, when I disable the breakpoints, and let the application run on its own, it crashes. How could break points possibly be affecting this?
Here is the relevant code:
- (NSInteger) numberOfSectionsInTableView:(UITableView *)songTableView{
return 1;
}
- (NSInteger)tableView:(UITableView *)songTableView
numberOfRowsInSection:(NSInteger)section{
bool debug = false;
if (debug) NSLog(#"--TableView: rankings");
if (expandedRow == -1)
return [self.songs count];
else //one row is expanded, so there is +1
return ([self.songs count]+1);
}
- (UITableViewCell *)tableView:(UITableView *)songTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
bool debug = false;
if (debug) NSLog(#"--tableView: tableView");
NSUInteger row = [indexPath row];
if (row == expandedRow){ //the expanded row, return the custom cell
UITableViewCell *temp = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"test"];
return temp;
}
UITableViewCell *cell = [tableViewCells objectAtIndex:row];
return cell;
}
}
- (NSString *)tableView:(UITableView *)songTableView
titleForHeaderInSection:(NSInteger)section{
//todo: call refresh title
return #"The Fresh List";
}
- (CGFloat)tableView:(UITableView *)songTableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44.0; //same as SongCell.xib
}
- (void)tableView: (UITableView *)songTableView
didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
bool debug = true;
//todo: if the user selects expanded cell, doesn't do anything
SongCell *cell = (SongCell *)[songTableView cellForRowAtIndexPath:indexPath];
if (cell->expanded == NO){
//change cell image
cell.bgImage.image = [UIImage imageNamed:#"tablecellbg_click.png"];
cell->expanded = YES;
//add new cell below
NSInteger atRow = [indexPath row] + 1;
NSIndexPath *insertAt = [NSIndexPath indexPathForRow:atRow inSection:0];
NSArray *rowArray = [[NSArray alloc] initWithObjects:insertAt, nil];
if (debug) NSLog(#"Expanded row: %d", atRow);
expandedRow = atRow;
[tableView insertRowsAtIndexPaths:rowArray withRowAnimation:UITableViewRowAnimationTop];
}else { //cell is already open, so close it
//change cell image
cell.bgImage.image = [UIImage imageNamed:#"tablecellbg.png"];
cell->expanded = NO;
NSIndexPath *removeAt = [NSIndexPath indexPathForRow:expandedRow inSection:0];
NSArray *rowArray = [[NSArray alloc] initWithObjects:removeAt, nil];
if(debug) NSLog(#"--about to delete row: %d", expandedRow);
expandedRow = -1;
[tableView deleteRowsAtIndexPaths:rowArray withRowAnimation:UITableViewRowAnimationTop];
//remove expaned cell below
}
}
This is just a guess, but it's a good idea to wrap code that changes the table structure in calls to
[tableView beginUpdates];
[tableView endUpdates];
I bet this returns null: [NSIndexPath indexPathForRow:expandedRow inSection:0]; and if it does it blows ...
hth
I hate to answer my own questions but I figured it out.
I was loading up my tableView from an array of objects. When I added the cell, that array still only held 30 objects, whereas my table held 31. I was correctly returning the numberOfRowsInSection method.
Here is the modification I made. Notice the extra else if:
NSUInteger row = [indexPath row];
if (row == expandedRow){ //the expanded row, return the custom cell
if(debug) NSLog(#"row == expandedRow");
UITableViewCell *temp = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"test"];
return temp;
}
else if (expandedRow != -1 && row > expandedRow)
row--;
My array of objects and the UITableViewCells were suppose to match up 1-1. After the expanded row, indexPath's row because off by 1. Here is my quick fix to this problem, although I'm sure there is a better way to solve this.
I have a UITableView which loads data from XML using the below code. It loads the information correctly, but the final two cells from the array are a repeat of the first two cells. I don't know if I'm loading it incorrectly; I NSLog out the values as I build the array and they are correct. I've also logged the final array and that's fine.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
ViewRoutesCell * aCell = (ViewRoutesCell *)[tableView dequeueReusableCellWithIdentifier:#"ViewRoutesCell"];
if (aCell == nil)
{
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"ViewRoutesCell" owner:self options:nil];
for (NSObject *anObj in arr) {
AssessObject *newObj1 = [[AssessObject alloc] init];
newObj1=[totalArray objectAtIndex:indexPath.row];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:newObj1.routeImage]]];
aCell = (ViewRoutesCell *)anObj;
aCell.imgRoute.image = image;
aCell.lblRouteText.text = newObj1.routeText;
aCell.lblRouteImage.text = newObj1.routeImage;
}
}
return aCell;
}
It's loading to a custom cell. It seems to me that the problem may be related to the reuse of the cells. Any ideas?
You must re-write your code as following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
ViewRoutesCell * aCell = (ViewRoutesCell *)[tableView dequeueReusableCellWithIdentifier:#"ViewRoutesCell"];
if (aCell == nil)
{
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"ViewRoutesCell" owner:self options:nil];
aCell = (ViewRoutesCell *)[arr objectAtIndex:0];
}
AssessObject *newObj1 = [[AssessObject alloc] init];
newObj1=[totalArray objectAtIndex:indexPath.row];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:newObj1.routeImage]]];
aCell.imgRoute.image = image;
aCell.lblRouteText.text = newObj1.routeText;
aCell.lblRouteImage.text = newObj1.routeImage;
return aCell;
}
otherwise, you cells will be cached with old values.
You have to assign the value to the cell if aCell != nil
if aCell != nil, it means that it find a reusable cells from previous created.
Therefore aCell keep what you created before and show you the repeat value
The previous answers are not very clear.
So lets step through it.
Reuse
When a table cell goes off the screen (e.g. is no longer visible) it goes into a pool of cells that are available for reuse these cells are tagged with a reuseIdentifier which may be like in your example #"Cell".
Why do we need a pool?
alloc/init'ing objects is expensive and when scrolling a UITableView you could potentially be doing this a lot especially if the user scrolls fast. This will cause your UITableView's scrolling to become slow and jerky.
So how do I reuse?
The method tableView: cellForRowAtIndexPath: main purpose in life is to give your UITableView a cell to display.
So the first thing you do in this method is call the method dequeueReusableCellWithIdentifier:. The dequeueReusableCellWithIdentifier: method will check the pool of cells available for reuse and return nil if there are none or return a UITableViewCell for you to configure.
The following check:
if (aCell == nil) {
is used to determine if we have a cell we can just configure or if we need to create a new one.
If aCell is nil then there was not a UITableViewCell available for reuse therefore we need to create one and give it it's initial config. This is a great time to set things like colours and other properties that only need to be set once.
After this if block we know we have a UITableViewCell that we just need to configure so now you can go ahead and set any labels or properties that are different for every cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
ViewRoutesCell * aCell = (ViewRoutesCell *)[tableView dequeueReusableCellWithIdentifier:#"ViewRoutesCell"];
if (aCell == nil) {
// Create a new cell and do any one time config
}
// Configuration that is different for every cell
return cell;
}
Just make sure that you always explicitly set all aspects of the cell's content in your -tableView:cellForRowAtIdexPath: implementation. If you fail to do that, any views in the cell that you fail to set will continue to have whatever content they acquired in their previous use.
im new to xcode and i am doing this code to fill up the table view with annotation titles but the function gets called more than once and the table cells are filled with all repeated values , how is the function called in xcode , how can i stop this function from getting called more than once
- (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];
}
NSLog(#"this is a test text ");
NSMutableArray *annotations = [[NSMutableArray alloc] init];
int i=0;
if(indexPath.section == 0)
{
for(iCodeBlogAnnotation *annotation in [map annotations])
{
i++;
NSLog(#"this is the no %d",i);
[annotations addObject:annotation];
}
cell.textLabel.text = [[annotations objectAtIndex:indexPath.row] title];
}
return cell;
}
Any help would be deeply appreciated ,
Thank you for your help in advance
You can't really control when it's called. It's called each time your tableview wants to display a new cell. You use the indexPath to determine what to put in that cell. It's called at least once per cell that's on the screen (sometimes more if the table is scrolled up and down).
You don't need to create the temporary array each time this function is called, just use [map annotations] directly :
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// There will be one row per annotation
return [[map annotations] count]
}
- (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];
}
// Put the text from this annotation into this cell
cell.textLabel.text = [[[map annotations] objectAtIndex:indexPath.row] title];
return cell;
}
I hope I've understood your question. If not, please tell me in the comments below!
It isn't a function, it is a method.
It is called by a table view as the table view draws cells. It will be called once per cell and, sometimes, more than once per cell depending on what the user is doing.
You don't push data into a table view, it asks you for cell contents.
Asking "how can i stop this function from getting called more than once?" indicates that you don't understand table views (it is confusing if you've come from the "push" model of UI programming). Start with the TableView programming guide.
The function is called whenever the UITableView does not already have a UITableViewCell for a particular index path and needs one. Note that it may be called multiple times for an index path, as a result of user scrolling (to save memory, cells that are offscreen may be reused or released) or calls to reloadData and related functions or insertRowsAtIndexPaths:withRowAnimation: and related functions. You cannot (and really do not want to) prevent it from being called more than once.
That said, assuming that [map annotations] returns an ordered collection of some sort that is ordered the same way each time, your code should be doing what you want (even though it is very inefficient). More detail on the problem would be helpful.