I am currently retrieving objects from Parse.com and saving them to CoreData.
I am then next using NSFetchedResultsController to retrieve objects from CoreData. These objects will then be used to create a table view. Everything i retrieve from CoreData is stored in an NSArray using the following code:
NSArray *fetchedObjects = _fetchedResultsController.fetchedObjects;
Using the fetched objects array i am wanting to load a specific nib file depending on the type of each object. So using the following for loop within cellForRowAtIndexPath i am trying to achieve this:
for (NSManagedObject *o in fetchedObjects)
{
if ([[o valueForKey:#"type"] isEqual: #"Type1"])
{
Type1CustomCell *cell = (Type1CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"type1CustomCell"];
return cell;
}
else if ([[o valueForKey:#"type"] isEqual: #"Type2"])
{
Type2CustomCell *cell = (Type2CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"type2CustomCell"];
return cell;
}
}
The previous code is just an example using 2 types, but within the app there may be more.
The return statement cause the loop to end, which means the loop never gets past the first object. Could someone please give me a point in the right direction of how to load multiple nib files depending on the type of the object I have retrieved?
Thanks
So, the only time you dequeue and return reusable collection view cells is in the datasource method that asks for a cell.
When this method fires, it's given you a specific index path--the index path for the row it's trying to create.
You don't need to be looping through anything in this method. You just need to go to the right index of whatever collection you're storing your data in, grab the object at that index. Use that data to determine what cell to return.
Instead of a forin loop, just grab a single object.
NSManagedObject *obj = [fetchedObjects objectAtIndex:indexPath.row];
if ([[obj valueForKey:#"type"] isEqual: #"Type1"]) {
// etc...
You'll still need a large if-else structure here, I believe, but now we're just checking an object at the specific index the table view is trying to create the cell for.
Related
I have a custom TableViewCell that I am getting data from a database in an asynchronous function that returns a UserObject that has the data I need in it. My problem is that the cellForRowAtIndexPath is returning the cell before that Asynchronous block is completed. How do i solve this problem?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"ImageWorkoutCellCollapsed";
ImageWorkoutCellCollapsed *cell = (ImageWorkoutCellCollapsed *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if(cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"ImageWorkoutCellCollapsed" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
WorkoutObject *workout = [[WorkoutObject alloc]init];
workout = [appDelegate.workoutObjectsArray objectAtIndex:indexPath.row];
cell.workoutTitle.text = workout.workoutTitle;
cell.workoutViewCount.text = workout.workoutDescription;
__block UserObject *cellUserObject = [[UserObject alloc]init];
[dbPointer getUserObjectFromDB:workout.workoutOwnerID completion:^(UserObject *result)
{
cellUserObject = result;
}];
cell.userName.text = cellUserObject.username;
return cell;
}
You should turn the cell update into a reload of the row as follows:
1) You should use dequeueReusableCellWithIdentifier:forIndexPath rather than the version you are using.
2) You should not need to check (cell == nil) as 1) should never return nil.
3) Add a new NSMutableDictionary property to cache UserObject's by workoutOwnerID.
4) When you are asked for a cell, lookup the dictionary from 3) to see if it has your data object. if not, then run the DB query. If it has the object, set the cell values.
5) In your completion handler for the DB lookup, cache the returned object into the new dictionary by workoutOwnerID. Then simply request the table to reload the row.
The result is that the cells are updated when the data they represent is updated.
You should make sure cellForRowAtIndexPath is not being called before the data is available.
You have a count of the number of rows, you must be setting this count to N but you haven't yet fetched N data items.
Don't set this number until you have fetched all the data.
OR
As data arrives continually update the number or rows and refresh the table view.
OR
Return the cell with placeholder data. Then once the actual data for the cell is available update the content and refresh the table.
OR
All of the above solutions involves moving the data fetch out of cellForRowAtIndexPath. However IFF the call to fetch the data is quick and thus won't slow down the drawing of the table, you need to convert the fetch from being asynchronous to synchronous. But it is not good design for a view controller component to directly access a db, instead it should be going to a model and the model should abstract away the implementation detail that the data is in a database.
Ok so I have very little experience with programming so please be patient. Let me explain further...I have a player class that has different properties. In the table view I number first name and last name of the player. I want to loop through each cell, and take the number first name and last name and add it to a new array. I don't want to be just add them as strings. I want to put them into a pickerview after ward. I have an dictionary that matches each number to a player ID. How would i do all of this? I have a loop that goes through tableviewcells which is like this
for (_tableViewCell in self.homePlayers.visibleCells)
{
if (_tableViewCell.accessoryType == UITableViewCellAccessoryCheckmark)
{
[_homeConfirmedPlayersArray addObject:[NSString stringWithFormat:#"%d %# %#",_homePlayer.number,_homePlayer.firstName,_homePlayer.lastName]];
}
}
homePlayers is the tableView that I am looping through. The problem is that it does go through each cell but it only takes the data from the last cell and adds it the new array once for each cell. I end up with 8 objects of the number first name and last name.
I set up the homePlauer object in cell for row at index path like this...
if ([tableView isEqual:self->_homePlayers])
{
static NSString *cellIdentifier = #"cell";
//Step 1: Check to see if we can reuse cell from a row that is now off the screen.
_tableViewCell = [_homePlayers dequeueReusableCellWithIdentifier:cellIdentifier];
//Step 2: If no reusable cells create a new one
if (_tableViewCell == nil)
{
_tableViewCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
//Add detail view accessory
_tableViewCell.accessoryType = UITableViewCellAccessoryCheckmark;
//Step 3: Set up cell text
_homePlayer = _homePlayersArray[indexPath.row];
_tableViewCell.textLabel.text = [NSString stringWithFormat:#"%d %# %#",_homePlayer.number,_homePlayer.firstName,_homePlayer.lastName];
//Step 4: Return the cell
return _tableViewCell;
}
If you need anymore information please ask. Thanks for the answers ahead of time!
It looks like you did a problem a lot of new programmer do, globalize everything. _homePlayer looks like it should be a local variable. What I assume is happening is when the UITableView is populating itself by calling tableView:cellForRowAtIndexPath:, the last cell that will be generated will set the global _homePlayer. Then when you other loop function gets called, _homePlayer will have already been set, and you also never change it in your loop function. That's why you get the same 8 objects. Here's how to fix it:
Make step 3 this:
id homePlayer = _homePlayersArray[indexPath.row];
tableViewCell.textLabel.text = [NSString stringWithFormat:#"%d %# %#", homePlayer.number, homePlayer.firstName, homePlayer.lastName];
You should replace 'id' with the homePlayer type so the compiler will assist you with auto completion.
I think you mentioned it was a NSDictionary, so replace id with NSDictionary *.
For your loop function do this: (comments for explanation)
// create a new and empty array
// (your local array will forever fill up with repeated objects unless emptied somewhere)
NSMutableArray *homeConfirmedPlayersArray = [[NSMutableArray alloc] init];
// loop through all indexpaths for the visible cells
for (NSIndexPath *indexPath in [self.homePlayers indexPathsForVisibleRows]){
// get the tablecell, only to check the accessory type
UITableViewCell *cell = [self.homePlayers cellForRowAtIndexPath:indexPath];
// I don't think this is necessary, especially if every cell has a checkmark
if (cell.accessoryType == UITableViewCellAccessoryCheckmark){
// get the record from the home players array
NSDictionary *homePlayer = _homePlayersArray[indexPath.row];
// add to the copy
[homeConfirmedPlayersArray addObject:[NSString stringWithFormat:#"%d %# %#",homePlayer.number,homePlayer.firstName,homePlayer.lastName]];
}
}
Remember to only use global variables only when you need them to be global.
I have a dynamic populated list. I am trying to have it return nothing if a variable is a certain value.
Here is what I am doing:
/* FIRST VALIDATE TIME LEFT TO MAKE SURE IT STILL EXIST */
NSString *check = [self.googlePlacesArrayFromAFNetworking[indexPath.row] objectForKey:#"time_left"];
if(![check isEqualToString:#"expired"])
{
return cell;
}
else
{
return NULL;
}
Now if expired exist than it returns NULL but that does not work. It crashes with the following error:
'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
Not sure how I can fix this, suggestions and thoughts?
David
UPDATE:
cell:
static NSString *CellIdentifier = #"timelineCell";
FBGTimelineCell *cell = (FBGTimelineCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[FBGTimelineCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[cell initTimelineCell];
Your data source must return consistent values across all of its methods. The number of rows in your tableView is controlled by the return value from numberOfRowsInSection and cellForRowAtIndexPath must return a valid cell for each row.
Validation and modification of the data in the data source must be performed outside of cellForRowAtIndexPath and [tableView reloadData] or [tableView deleteRowsAtIndexPaths] called to update the table display
Update
It is hard to provide an example without understanding what is driving the "expired" behaviour - Is this data that is retrieved from the web service or is it the result of information ageing after it is retrieved. If the former then I would suggest that you transfer the data from the web service result array into an array that drives the tableview and filter the expired data while you are copying it. If you need to periodically scan for expired data then you can use something like the following -
You would need to have something, such as an NSTimer trigger this method periodically, or if you are re-fetching data from the network, that could be the trigger to run this method -
-(void)expireData
{
for (int i=0;i<self.googlePlacesArrayFromAFNetworking.count;++i) {
NSDictionary *dict=[self.googlePlacesArrayFromAFNetworking objectAtIndex:i];
if ([[dict objectForKey:#"time_left"] isEqualToString:#"expired"])
{
[self.googlePlacesArrayFromAFNetworking removeObjectAtIndex:i];
[self.tableView deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForItem:i inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
--i; // Array is now one element smaller;
}
}
}
Note that this method modifies the self.googlePlacesArrayFromAFNetworking array. If this is unacceptable then you need to copy this array to another array to drive the UITableView.
Another approach is to scan the array in numberOfRowsInSection and work out how many non-expired elements there are and return that. Then in cellForRowAtIndexPath you need to scan forward through the array to find the next non-expired element; This is pretty messy though because your indexPath rows and your array indices will be out of sync.
It depends on what you want. There are two options:
If you want your table view to have empty rows for the data that are expired, you need to configure and return a blank cell.
If you want those cells to be missing, you need to return the correct number of rows for -tableView:numberOfRowsInSection (that is, subtract out the number of expired rows in your calculations before returning from that method). Then, your data source will never be asked for a cell for that index path.
Update: by the way, you should return nil, not NULL, when the parameter is expecting an Objective-C object. nil is equivalent to (id)0, whereas NULL is equivalent to (void *)0. [source]
You always have to return a cell. What you need is not return nil but just don't populate it.
I am trying to add all of the cell.img.tag to my nsmutable array. However, I tried multiple ways of doing this such as for loop, but the result was incorrect each time. I need each individual tag added to the NSMutableArray. I will post my code below to give you an idea. Note: this is just to show what I am doing, this wasn't the method I tried to use to make this work.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
Cell *cell = (Cell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
NSString *string = [addressString valueForKey:#"number"];
array = [[NSMutableArray alloc]init];
cell.img.tag = string.intValue;
[array addObject:[NSNumber numberWithInt:cell.img.tag]];
return cell;
}
Edit: When I used the for loop it will show all the tags, but just ended up creating multiple arrays with same value in each array repeated multiple times like so.
(1,1,1)(2,2,2).
This line is the problem:
array = [[NSMutableArray alloc]init];
Each time the cellForItemAtIndexPath function is called you're basically destroying the array and creating a new empty one.
Move that line somewhere else in your code (in the init method, for example).
There are two problems with the code. The first is the array allocation each time the datasource fetches a cell.. I suspect this is not really in your implementation, since you say "this wasn't the method I tried to use to make this work."
The other problem, the reason you see the same object over and over is that the part of the model you fetch NSString *string = [addressString valueForKey:#"number"]; is invariant with respect to the indexPath passed to the datasource.
Do you have an array of addresses? This would make more sense:
NSString *addressString = self.myArrayOfAddressesWichIsMyModel[indexPath.row];
NSString *string = [addressString valueForKey:#"number"];
Now string will change each time. Re-examine your datasource method collectionView:numberOfItemsInSection: You're probably answering the count of some array. That's the array that must be dereferenced by indexPath.row in your cellForItem method.
I am new in iPhone and now I am struggling to create and inserting object.
I explain first
Here I have one tableview with some cell value which is getting from my array whose name is appDelegate.array1
now I can change some value in table view cell and now after that I want to insert this new cell value in same array appDelegate.array1. So how can I do this.
I tried to do it like this.
-(void)viewWillAppear:(BOOL)animated
{
[appDelegate.array1 addObject:indexPath];
[tableView reloadData];
}
it is correct or not if yes then why my application will terminate and if not then please give me correct method to add object in array
Note this points.
1) Check if your array is Mutable array...Then only you can add new items to array at run time
2) What is indexPath in your code, which is bit confusing..
3) To add object you use these (or more)functions
[yourArray addObject:your_object];
Or to insert into the array you can use
[yourArray insertObject:your_object atIndex:your_index];
Hope this help
Instead of reloading entire table view for adding just one item, Just add the element to the array and update the table view....
-(void) addObject : (id) inObject
{
if(inObject)
{
[appDelegate.array1 addObject:inObject];
[mTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}
}
How are you declear your array?
you need to make it a NSMutableArray.
NSMutableArray *array1;
Your method is right but there can be number of problems in ur logic.
Like you add indexPath. (what is in indexPath?) is it nil or something like that.
your appDelegate object correctly initialized or not.
There is certainly a problem in your logic.