How exactly does the NSIndexPath row property work - nsindexpath

I'm reading through the Big Nerd Ranch iOS guide and I have a question about this piece of code here:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"UITableViewCell"];
}
BNRItem *p = [[[BNRItemStore sharedStore] allItems] objectAtIndex:[indexPath row]];
[[cell textLabel] setText:[p description]];
return cell;
}
BNRItemStore is just a data store object and I've already added five BNRItem objects to it in the initializer method. Their string descriptions are then printed to the UI. My confusion specifically is about this line here:
BNRItem *p = [[[BNRItemStore sharedStore] allItems] objectAtIndex:[indexPath row]];
The way I understand this line is that objectAtIndex: just retrieves the items in the BNRItemStore and assigns them to the variable. The question I have is this:
How is objectAtIndex: able to return all five objects to the variable *p with the argument [indexPath row]? I was under the impression that an indexPath object holds a single section and a single row. So the row property would just return a single row index. Here it looks like the array is being looped through and its 5 contents returned to the variable, which are then printed to the UI. Or is that not what's going on? What is the row property actually doing here?

Your understanding is correct. NSIndexPath encapsulates a section and a row. I think your confusion is that BNRItem *p is not pointing to all 5 items (it's only pointing to one at a time)... rather, the method tableView:cellForRowAtIndexPath: is called for each row that is being displayed in the Table View.
There is another method, tableView:numberOfRowsInSection: which is called. I assume this method is returning the number 5, so tableView:cellForRowAtIndexPath: is called 5 times... each time the indexPath will have a different row, and thus print a different object.

Related

Use property outside block: findObjectsInBackgroundWithBlock:

I'm using parse.com as database and I have a query to retrieve data. Although I see multiple threads about this, I can't figure out the solution to my code.
I run the query inside viewDidLoad of my tableViewController.
PFQuery *exerciciosQuery = [PFQuery queryWithClassName:#"_User"];
[exerciciosQuery includeKey:#"exercicios"];
[exerciciosQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
exerciciosArray = objects;
[self.tableView reloadData];
NSLog(#"%#", objects);
}];
And I need to show the tableView according to the result of query:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// Configure the cell
PFObject *exercicios = [exerciciosArray objectAtIndex:indexPath.row];
[cell.textLabel setText:[exercicios objectForKey:#"Title"]];
return cell;
}
The problem is, the property *exerciciosArray is null if not inside the block. Therefore, I'm not sure how to work around this to show the results of the query on my tableviewcells.
Thanks
Doing exerciciosArray = objects; doesn't do what you think. You're assigning to a copy of exercisiosArray, not the original variable or property. That copy only exists inside the. You can use __block in the ivar declaration. That will tell the runtime to use the original variable in the block, and not create a copy.
[EDIT] The above paragraph is incorrect. Ivars that are used inside the block are never copied, instead they're implicitly referenced through self, which is retained in the block. Adding the __block modifier to an ivar doesn't make any difference. Assigning to an ivar inside a block is OK. This is a somewhat subtle point of Objective-C that is not well documented.
So you have two options:
Add __block to the declaration (if exerciciosArray is an iVar). [EDIT] This is unnecessary. See the edit above.
If exerciciosArray is a property, make exerciciosArray an NSMutableArray. Then, you can elements to it by doing [exerciciosArray addObjectsFromArray:objects];
Don't forget to do [[exerciciosArray alloc] init] where appropriate.

How to populate multiple table view sections from the same .plist

Sorry if this has been discussed already, I couldn't find what I was after..
I have a .plist file which has 2 arrays in it, which are split exactly how I would like my sections split in my table view.
I have no problem getting an array into a table view, but I cant get my head around how to tell the app that I want one array in the first section and the second array in the second section.
My code at the moment to get an array into a table is this (and it works fine):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Create string with reference to the prototype cell created in storyboard
static NSString *CellIdentifier = #"PlayerNameCell";
//Create table view cell using the correct cell type
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//Create a dictionary containing the player names
NSDictionary *players = (NSDictionary*) [[self Squad] objectAtIndex:[indexPath row]];
//Set the cell text to the player name
[[cell textLabel] setText:(NSString*)[players valueForKey:#"PlayerFullName"]];
//Set the cell detail text to the squad number
[[cell detailTextLabel] setText:(NSString*)[players valueForKey:#"PlayerSquadNumber"]];
return cell;
}
But now I have another table view where I'll need 2 sections, each reading from different arrays.
Any help would be greatly appreciated.
Many thanks
Just do the following:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"PlayerNameCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if( cell == nil ){
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if( indexPath.section == 0 ){
NSDictionary *players = (NSDictionary*) [self.array1 objectAtIndex:indexPath.row];
cell.textLabel.text = (NSString*)[players valueForKey:#"PlayerFullName"];
cell.detailTextLabel.text = (NSString*)[players valueForKey:#"PlayerSquadNumber"];
} else {
NSDictionary *players = (NSDictionary*) [self.array2 objectAtIndex:indexPath.row];
cell.textLabel.text = (NSString*)[players valueForKey:#"PlayerFullName"];
cell.detailTextLabel.text = (NSString*)[players valueForKey:#"PlayerSquadNumber"];
}
return cell;
}
Set the number of sections to 2. For each section get the different values with [self.array2 objectAtIndex:indexPath.row].
I don't know how you are saving the plist into 2 arrays, but if you need help with that let me know.
Okay, so you have 2 arrays at the top level, and each of those arrays contains arrays, right?
You've got a couple methods you need to adjust. Return the number of top level arrays for the number of sections in the table view. In cellForRowAtIndexPath:, return the correct object for the correct section/row. Something like
[[sectionsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]

Objective-C visibleCells and indexPathsForVisibleRows

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

UITableView showing (null) for first row

I am updating a UITableView with CoreData. On startup, the first row is showing as (null). If I scroll down/up, it loads though. It just doesnt show initially.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
[cell setAccessoryType: UITableViewCellAccessoryDisclosureIndicator];
}
// set text for each cell
[cell.textLabel setText: [dataManager getProjectValueForKey: #"Title" atIndex: [indexPath row]]];
[cell.detailTextLabel setText: [[[dataManager getProjectValueForKey: #"pid" atIndex:[indexPath row]] stringByAppendingString: #": "] stringByAppendingString: [dataManager getProjectValueForKey: #"Sponsor" atIndex:[indexPath row]]]];
return cell;
}
dataManager is what is talking to Core Data. I feel like it might be lagging behind or something on startup so the cell is trying to show the data before it is ready. But I don't know.
Actually, there is a great "dataManager" already built into Core Data. It is called NSFetchedResultsController. You can always retrieve the correct data object for your table cell with
[[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
This is typically done inside tableView:cellForRowAtIndexPath:, and not in some other helper function. The fetched results controller will take care of everything that is necessary to retrieve the data that is needed in order to display it.
See also the numerous Core Data code examples from Apple for this common design pattern.

how is the function called in xcode

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.