get some Error with creating and inserting object of array - ios

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.

Related

How to achieve UITableView row animation similar to Mail

In Mail, if you manually refresh a mailbox via pull to refresh, if you don't have any new emails, the view slides back up to hide the spinner but the appearance of existing cells don't change - they don't animate at all. But if you did have new emails, those animate in from the top. If you had no emails listed before the refresh, all the cells animate in quite nicely. Also, if you refresh and an existing email had been deleted, its disappearance is animated but none of the other cells animate besides the entire table moving up to take the place of the removed cell. I would like to achieve this same behavior in my app.
Currently, when my data is fetched for the first time, it immediately appears in the table without any animation. If some data is deleted and the user manually refreshes, the entire table is instantly updated so there is no animation of the cell disappearing, it's just instantly replaced with the row underneath it. The same behavior occurs if cells were already displayed and refreshing results in new data being added to the table - it just instantly appears.
How can I implement animation for appearance and disappearance of cells when I reloadData? But not animate existing cells if they don't change.
My setup is to fetch the data, parse the JSON into a data structure (array or dictionary), then call reloadData, and then cellForRowAtIndexPath gets the data from the structure. When the user refreshes, it performs those same steps again.
My attempts so far haven't been successful. I tried instead of simply calling reloadData, I call [self.tableView reloadSections:[NSIndexSet indexsetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop]; but this only animates the first section and it always animates it, even if none of the data has changed. My other attempt also always animates it:
[self.tableView beginUpdates];
[self.tableView deleteSections:[NSIndexSet indexsetWithIndex:0] withRowAnimation:YES];
[self.tableView insertSections:[NSIndexSet indexsetWithIndex:0] withRowAnimation:YES];
[self.tableView endUpdates];
When inserting the rows, keep the index paths being inserted in an array, then:
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:arrayOfInsertedIndexPaths withRowAnimation: UITableViewRowAnimationTop];
[self.tableView endUpdates];
I haven't confirmed directly, but I'm pretty sure this will behave as you wish when now insertions are made.
EDIT - Row-accurate animation is going to require row-accurate knowledge about the change in the model. How to get the index paths being inserted depends on how you're getting the data. In the toughest scenario, you're just getting a new collection and will need to work out for yourself what has changed. Your model objects probably need to implement equivalence** so you can conclude that different instances should be treated as the same.
** as in NSString isEqualToString: which is an equivalence test, not an equality test as it's name might suggest.
We're comparing the objects themselves, not row indexes, so knowing about deletions is hard, but no harder than knowing about insertions. As an example, here's a category I created for NSMutableArray that handles only insertions (it's ideal for an email or other timeline sort of app that's expecting new stuff, not deletions).
#import "NSMutableArray+Inserts.h"
#implementation NSMutableArray (NSMutableArray_Inserts)
// insert elements of array into the receiver, sorting on a key
// answer an array of index paths where the insertions happened
- (NSArray *)insertFromArray:(NSArray *)array sortedOn:(NSString *)key ascending:(BOOL)ascending {
// first insert everything from array (preserving uniqueness according to shallow equality)
for (id newElement in array) {
NSInteger position = [self indexOfObject:newElement];
if (position == NSNotFound) [self addObject:newElement];
}
// now sort using key
[self sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:key ascending:ascending]]];
// now find the insertions using deep equality
NSMutableArray *answer = [NSMutableArray array];
for (id newElement in array) {
NSInteger position = [self indexOfObject:newElement];
id element = [self objectAtIndex:position];
if (element == newElement) {
[answer addObject:[NSIndexPath indexPathForRow:position inSection:0]];
}
}
return [NSArray arrayWithArray:answer];
}
A couple of notes: 1) I lazily hardcoded the index path section to 0. If your model has multiple sections, you'll want to call this for each and add a section parameter so you get good index paths back, 2) the sorted insert feature of this is pertinent to my particular app, you might not need it, and can just remove those two params and delete the self sortUsing... line. 3) one way to account for deletions would be to add an analogous method that answers the index paths of elements in the receiver NOT in a passed array.

Multiple Nib files in UITableView

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.

Returning nill cell based on data

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.

Dealing with UITableView's indexPathsForSelectedRows

I'm not sure how I can implement that my mock UITableView object answers correctly for indexPathsForSelectedRows.
In my App the user can (in editing state) select cells in a table view, which represents the files/folders of a given directory.
Once the user selects a folder item the previously selected files items should be deselected. My test (using OCHamcrest/OCMockito) looks like this.
- (void)test_tableViewwillSelectRowAtIndexPath_DeselectsPreviouslySelectedCells
{
// given
[given(self.mockTableView.editing) willReturnBool:YES];
// when
[self.sut tableView:self.mockTableView willSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:SectionIdFile]];
[self.sut tableView:self.mockTableView willSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:SectionIdFolder]];
// then
}
The problem is that I can verify that the file item is selected but I can't ask the the mockTableView for its selected rows. Could somebody tell me how to handle that? Do I have to record the tableView:selectRowAtIndexPath:animated:scrollPosition: calls myself and provide the correct answer when the tableView is asked for that information?
As the mockTableView can not record (like the real UITableView) the indexPath's of the selected cell, you have to make sure that the mock object returns the correct answer for that method. So in my case the test looks now like this.
- (void)test_tableViewwillSelectRowAtIndexPath_DeselectsPreviouslySelectedCellsForSectionIdFile
{
// given
[given(self.mockTableView.editing) willReturnBool:YES];
NSArray *selectedRows = #[[NSIndexPath indexPathForRow:0 inSection:SectionIdFile], [NSIndexPath indexPathForRow:1 inSection:SectionIdFile]];
[given([self.mockTableView indexPathsForSelectedRows]) willReturn:selectedRows];
// when
[self.sut tableView:self.sut.myTableView willSelectRowAtIndexPath:selectedRows[0]];
[self.sut tableView:self.sut.myTableView willSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:SectionIdFolder]];
// then
[verify(self.mockTableView) deselectRowAtIndexPath:selectedRows[0] animated:YES];
[verify(self.mockTableView) deselectRowAtIndexPath:selectedRows[1] animated:YES];
}

Pattern for Updating UITableView on Sync?

I'm working on a feed reader iOS project. Feed entries are listed in a UITableView in reverse chronological order. On launch, they're loaded from a database into an array.
When the app syncs to feeds, it creates a new array for the new order of things, and then to update the table, compares the new array to the old array to determine what cells to delete, update, or insert. The way I've done it is naïve, and therefor really inefficient: Lots of calls to indexOfObject: to see if an item in one array is in the other array. Twice. once for each new entry as it's added to the new array, to see if it's in the old array, and then once for each entry in the old array, to see if it's not in the new array.
As a database professional, this design offends me.
But it must be a pretty common pattern. What would be the most appropriate, sufficiently Cocoa-ish way to go about this?
Turns out I was coming at this wrong. The solution I found was to add and remove items from the array as usual, and then to call insertRowsAtIndexPaths:withRowAnimation:, reloadRowsAtIndexPaths:withRowAnimation:, and deleteRowsAtIndexPaths:withRowAnimation: as appropriate for each row added, changed, or moved. The fault in my previous plan was the thought that I should wait until all the changes and been made, and then call each of those methods only once in a beginUpdates/endUpdates block. Turns out the block wasn't actually necessary, as the modification methods can be called outside of them.
It was much easier to call each method once for each cell inserted, updated, or deleted, than to calculate all the changes at the end and commit them at once. Was just too confusing, error-prone, and inefficient to try to do it all at once.
So the code I ended up with looks like this:
if (parsedItem.savedState == ItemModelSavedStateInserted) {
// It's a new entry. Insert it.
[items addObject:parsedItem];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:items.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationTop];
} else {
// It's an existing entry. Find it in the portal and move it, if necessary.
NSUInteger foundAt = [items
indexOfObject:parsedItem
inRange:NSMakeRange(currentItemIndex, items.count - currentItemIndex - 1)
];
if (foundAt == currentItemIndex) {
// It hasn't moved!
if (parsedItem.savedState == ItemModelSavedStateUpdated) {
// It was updated, so replace it.
[items replaceObjectAtIndex:currentItemIndex withObject:parsedItem];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:currentItemIndex inSection:0]] withRowAnimation:UITableViewRowAnimationMiddle];
}
} else {
// It has shifted position.
if (foundAt != NSNotFound) {
// It has moved.
[items removeObjectAtIndex:foundAt];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:foundAt inSection:0]] withRowAnimation:UITableViewRowAnimationBottom];
}
// Need to insert it.
[items insertObject:parsedItem atIndex:currentItemIndex];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:currentItemIndex inSection:0]] withRowAnimation:UITableViewRowAnimationTop];
}
}
Consider using NSSets for differencing the set of current items and the set of new items, with a single NSMutableArray to hold the ordered current list. You would probably want to remove each of the expired items from the array, then insort each of the unexpired new items into the array. The items that you needed neither to remove nor to insort are the items that you may want to update.

Resources