Use property outside block: findObjectsInBackgroundWithBlock: - objective-c

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.

Related

Issue with UITableView on scroll

I'm experiencing an issue with my UITableView which fetches data from a data table on parse.com. The issue is that every time I scroll down, hiding the first cell completely and then scroll back up, the text on the first cell's titleL is that of another cell. Kindly look at my code and let me know what I'm doing wrong. Also are there any better practices for my code when working with UITableViews in the future?
Code
- (void)viewDidLoad {
[super viewDidLoad];
[self someMethod];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifer = [NSString stringWithFormat:#"CellIdentifier%i",num];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
UILabel *titleL = [[UILabel alloc] initWithFrame:CGRectMake(10,10,300,20)];
titleL.text = myTitle;
[cell addSubview:titleL];
return cell;
}
-(void) someMethod {
for (int i = 0; i < arr.count; i++) {
PFQuery *query = [PFQuery queryWithClassName:#"SomeClass"];
[query whereKey:#"objectId" equalTo:[arr objectAtIndex:i]];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!object) {
} else {
myTitle = [object objectForKey:#"title"];
num = i;
[feed beginUpdates];
[feed reloadRowsAtIndexPaths:myArr withRowAnimation:UITableViewRowAnimationAutomatic];
[feed endUpdates];
}
}];
}
}
You need to write your your tableView:cellForRowAtIndexPath: in such a way that it doesn't matter in which order it is called.
That method is called whenever the UITableView needs to get a cell (sometimes this doesn't mean that it's displayed). It will get called multiple times and you cannot rely on a specific order (for obvious reasons: you cannot predict how the user will scroll).
Now, your problem is that your implementation uses myTitle to assign a title. But that value is not calculated inside tableView:cellForRowAtIndexPath:. You need to change your code in such a way that you always can access the required value for your index path, no matter in which order or how often that method is called.
For example, in someMethod you can store your values from [object objectForKey:#"title"] in an NSMutableArray or in a NSMutableDictionary (with #(i) as key). Then you can query the title for each index path in tableView:cellForRowAtIndexPath:.

How exactly does the NSIndexPath row property work

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.

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.