Reordering TableViewCells location doesnt persist - iphone

Im reordering my TableViewCells with the following code:
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath{
[self.ammoTable moveRowAtIndexPath:indexPath toIndexPath:[NSIndexPath indexPathForRow:[self.tableArray count] - 1 inSection:1]];
}
-(IBAction)setEdit{
if ([self.button.currentTitle isEqualToString:#"Edit"]) {
self.ammoTable.editing = YES;
[self.button setTitle:#"Done" forState:UIControlStateNormal];
}
else{
self.ammoTable.editing = NO;
[self.button setTitle:#"Edit" forState:UIControlStateNormal];
}
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath
*)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
// fetch the object at the row being moved
NSString *r = [self.tableArray objectAtIndex:fromIndexPath.row];
// remove the original from the data structure
[self.tableArray removeObjectAtIndex:fromIndexPath.row];
// insert the object at the target row
[self.tableArray insertObject:r atIndex:toIndexPath.row];
[self.ammoTable reloadData];
}
Everything works great, except when you leave the view and return to it. If i move the cells around, when the view comes back up, they are all in the original order that they were in. Is there any reason for this? Thanks for the help!

because you are reloading your tableview with old array i.e. not with updated array
that means you are reinitializing your array ,it may be in viewDidLoad.
create & initialize your array in appDelegate .Use this array & make changes when you moves cell,it will persist order of elements.
see this link to how to create & access array from appDelegate

I presume you are loading data from some sort of array. In addition to moving the cells in the table, you must also do the same to this array.

You need to persist the changes you make to the tableArray property so when you reload that array again the next time the view is displayed, the rows are loaded in the new order.

When you make changes in the tableview cells you also need to make same changes in the tablearray. otherwise tableview cells get loaded as per order in the array.

#Alex
Why is it that when i don't reorder the cells, the array automatically
remembers the order? But if i want to reorder i must manually make it
remember the state?
You see Alex, the array doesn't remember any order, it is the table that arranges itself according to the array. That is the whole point of the data source. Manipulate the data source and you can manipulate the table. But manipulating the table doesn't alter the datasource. So if you are making some changes to your table, remember to change the dataSource beforehand to reflect the same.
For eg. say you want to delete a row from the table. If you go to
commitEditing method and simply delete the row, you will end up
crashing, with an invalid update or inconsistency exception. You need
to first delete the data for the corresponding cell from the
dataSource, then delete the row.
Same in your case ..first interchange the objects in your data Array, then move the rows accordingly.
Hope I am clear. Cheers and have fun.

Related

Looping through UITableViewCells of a UITableView

So I've been trying to load cells from a UITableView and put those into an NSMutableArray to be iterated through later. I have a method called populateArrayWithCells which works fine until I put in more than 6 cells. For some reason the object returned is nil. My code is below, StocksAndAccounts is the UITableView and I have subclassed the UITableViewCells in section 1.
- (void) populateArrayWithCells
{
for (int i = 0; i < [StocksAndAccounts numberOfRowsInSection:1]; i++) {
if ([StocksAndAccounts cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:1]] == nil) {
NSLog(#"object at index %i is nil", i);
}
[stocksCells addObject:[StocksAndAccounts cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:1]]];
}
}
This function, when called through an IBAction, returns the cell successfuly until the index is 6, in which case it is nil and crashes because it's trying to add an object that is nil. Any help with this problem would be greatly appreciated.
I think you've misunderstood how UITableViews work. The cells in a tableview are only loaded when they need to be displayed, and they are recycled, so if you can only see six cells on screen at a time then the table will only ever contain six cells and it will just keep updating and reusing those same six as you scroll up and down.
Instead of storing data in your table cells, store it in an array of custom objects in your view controller and use those objects to populate the cells when the table requests them from your datasource methods.
That way if you ever need to use that data for something other than displaying in the table, you can re-create it from the original object instead of trying to copy it out of the table cell.
You are doing it backwards.
Your NSMutableArray (and preferably a sturdier model than that) should be holding what you want to display in your UITableView, not the other way around. Please review the Model-View-Controller design pattern.

Save the state of a table view/ save the state of the table view text

I have a UITableView and i can add and delete cells. I also have two buttons on each cell to add and subtract 1 from the cells text. But when i go to a different page and then back to the table view page, all the cells text is set back to 1. But i want the cell's text to stay at the value that the user had set it to! Could someone help me? Im not sure what to do whatsoever. Thanks!
You will have to maintain an NSMutableArray (probably of NSIntegers) that saves the values of the cells. Then, whenever the user changes the value, update the value in the array. Also, display the cell label values by reading from the array. Sample codes below-
-(void)plusButtonClicked:(id)sender
{
//figure out the cell index which was updated using sender
//update the array entry
//[self.array objectAtIndex:index]++; self.array is the array you will maintain
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.label.text = [NSString stringWithFormat: #"%d", [self.array objectAtIndex:indexPath.row]];
}
If you want the values to persist even after the app is terminated and restarted, consider using CoreData.

indexPath Adjustment iPhone

I have a UITableView that, under certain conditions, needs to have something added to the top of it. All data (except for what is inserted at the top of the UITableView under certain conditions) is brought in from an array.
Because everything is brought in from an array, I need to modify the indexPath that fetches those array objects each and every time the method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath is called. If I try to create a local variable and update the local version of indexPath.row, it tells me it is read only.
What would be the best way to implement this?
Drawing below (this is not intended to be code, but a drawing of the table view):
(REGULAR SITUATION) (3 lines)
array objectAtIndex:0;
-----
array objectAtIndex:1;
-----
array objectAtIndex:2;
etc. etc
(MODIFIED SITUATION) (4 lines)
blah blah modified insertion text here
-----
array objectAtIndex:0;
-----
array objectAtIndex:1;
-----
array objectAtIndex:2;
etc etc
Thanks in advance.
Why not just add your new item at the start of your array?
// Create a mutable copy and add the item at index 0
NSMutableArray *mutable = [myData mutableCopy];
[mutable insertObject:newItem atIndex:0];
// Then store the new array and reload the table
[myData autorelease];
myData = mutable;
[self.tableView reloadData];
Then you don't have to do anything funny at all with index paths :)
THIS is a great tutorial that addresses your issue.
Use
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
OR
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
Return the String/View as something or empty depending on the condition that you use while deciding when to show it or not.
Don't go down that path. Just use the tableHeaderView property of UITableView to add something on top of the table. It will scroll just like a UITableViewCell.
self.tableView.tableHeaderView = aView;
To remove it, just set it to nil.
If you insist in this method, just keep a BOOL around to tell you in which state you are and if you need the extra line just subtract 1 from indexPath.row, like
[myArray objectAtIndex:indexPath.row-1];

iPhone SDK: Inserting and updating a UITableView with a new row

I have a tableView that needs to be updated after information has been inserted from another view. If I perform a
[self.tableView reloadData];
The very next time I insert more information in another view and try to reload the table, all the currently visible rows are duplicated.
In other words, when I start up the app I have:
tableView:
Row 1
Row 2
Then I submit some information that will also show up in the table and suddenly I have:
tableView
Row 1
Row 2
Row 3 <- info I just added
Row 1
Row 2
My numberOfRowsInSection implementation looks like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [ItemsController sharedItemsController].count;
}
My cellForRowAtIndexPath implementation looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemsController* controller = [ItemsController sharedItemsController];
NSMutableArray* recentItems = controller.listOfRecentItems;
CustomCell *cell = nil;
NSUInteger row = [indexPath row];
if( row < recentItems.count )
{
Items* item = [recentItems objectAtIndex:row];
if( recentCellData == nil )
recentCellData = [[NSMutableDictionary alloc] initWithCapacity:[indexPath length]];
if( [recentCellData count] > 0 )
cell = [recentCellData objectForKey:[NSString stringWithFormat:#"%d", row]];
if (cell == nil) {
UIViewController * view1 = [[UIViewController alloc] initWithNibName:#"CustomCell" bundle:nil];
cell = (CustomCell*)[view1 view];
[recentCellData setObject:cell forKey:[NSString stringWithFormat:#"%d",row]];
}
// do some other stuff here
}
// Set up the cell
return cell;
}
What's the best way to update the table and avoid duplicating the currently visible rows.
Thank in advance for all the help!
The error isn't in how you're reloading the table, it's in how you're providing data to it. Set a breakpoint in the data source methods and the method that adds new rows to see where you're going wrong.
You'll only end up with five items if tableView:numberOfRowsinSection: returns 5. Thats the simple answer to your question, but I see other problems here. I'm wondering why you have this test: row < recentItems.count. Is that array the same thing as [ItemsController sharedItemsController].count? You really need to be using the same array for both methods.
(Also, it's not a syntax error, but you shouldn't use the property syntax for things that aren't declared as properties. You should write [recentItems count] instead.)
I'm also confused by the code you use to set up the cell. Cells are meant to be reusable. That is, you create one cell, then reconfigure it every time in your implementation of tableView:cellForRowAtIndexPath:. Your code creates a cell for each item in your list. This is very memory-inefficient, and will likely crash your program due to insufficient memory on the iPhone if you keep lots of cells in memory like this.
The recommended approach is to call dequeueReusableCellWithIdentifier:. If that returns nil, then you set up a cell using the initWithFrame:reuseIdentifier: initializer. The table view is very smart, and will only ask you to redraw the cell when it needs you to.
Your recentCellData dictionary looks really shaky to me, too. What if you insert an item after the item with key #"2"? All the items with key #"3" onward will need to be shifted one element to the right to work the way you expect. That's a ton of bookkeeping that seems rather unnecessary to me. If you really needed something like this -- and to be clear, I don't think you do -- why wouldn't you use an NSMutableArray, which is much easier to use?
I added a bit more info above.

What methods do I have to implement in order to re-arrange the UITableView rows?

For a simple example of using a NSMutableArray of strings called rows, what do I have to implement in my table controller to move the tableView rows and have the changes reflected in my array?
Here we do our heavy lifting.
- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath
{
NSLog(#"move from:%d to:%d", fromIndexPath.row, toIndexPath.row);
// fetch the object at the row being moved
NSString *r = [rows objectAtIndex:fromIndexPath.row];
// remove the original from the data structure
[rows removeObjectAtIndex:fromIndexPath.row];
// insert the object at the target row
[rows insertObject:r atIndex:toIndexPath.row];
NSLog(#"result of move :\n%#", [self rows]);
}
Since this is a basic example, lets make all the rows moveable.
- (BOOL)tableView:(UITableView *)tableView
canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
Neither of the above will work.
In the original posted code, the tableview will crash because it removes the data that will still be in use, and even after that was corrected, The array would not have the correct data due to the way table does a 'rearrange'.
exchangeObjectAtIndex:withObjectAtIndex: will not work for rearranging an array according to how a tableview implements its own rearranging
Why?
Because when the user selects a table cell to rearrange it, that cell does NOT get swapped with the cell they are moving it to. The cell they selected gets inserted at the new row index and then the original cell is removed. At least that's the way it appears to the user.
Solution:
Due to the way the tableview implements a rearrange, we need to perform a check to make sure we add and remove the right row.
This code I put together is simple and works perfect for me.
Using the original posted code data as an example:
- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath
{
NSLog(#"move from:%d to:%d", fromIndexPath.row, toIndexPath.row);
// fetch the object at the row being moved
NSString *r = [rows objectAtIndex:fromIndexPath.row];
// checks to make sure we add and remove the right rows
if (fromIndexPath.row &gt toIndexPath.row) {
// insert the object at the target row
[rows insertObject:r atIndex:toIndexPath.row];
// remove the original from the data structure
[rows removeObjectAtIndex:(fromIndexPath.row + 1)];
}
else if (fromIndexPath.row &lt toIndexPath.row) {
// insert the object at the target row
[rows insertObject:r atIndex:(toIndexPath.row + 1)];
// remove the original from the data structure
[rows removeObjectAtIndex:(fromIndexPath.row)];
}
}
If you take a few moments and take a look at what happens to a tableview during a rearrange you will understand why we add the 1's where we did.
I'm pretty new to xcode, so I know there is probably an easier way to do this, or the code can probably be simplified....Just trying to help out where I can because it's taken me a few hours to figure this out. Hope this saves someone some time!
According to Apple's documentation, and my own experience, this is some simple code that works quite well:
NSObject *tempObj = [[self.rows objectAtIndex:fromIndexPath.row] retain];
[self.rows removeObjectAtIndex:fromIndexPath.row];
[self.rows insertObject:tempObj atIndex:toIndexPath.row];
[tempObj release];
NSMutableArray has a method called exchangeObjectAtIndex:withObjectAtIndex:.
Found this discussion after breaking my head against a simple implementation... Michael Berding solution is the best for me, the Apple way, just remember to remove retain and release when using ARC. So, a more concise solution
NSObject *tempObj = [self.rows objectAtIndex:fromIndexPath.row];
[self.rows removeObjectAtIndex:fromIndexPath.row];
[self.rows insertObject:tempObj atIndex:toIndexPath.row];