Why does reordering my tableView mess up my data? - iphone

I have two different table views in which I use the exact same code in tableView:MoveRowAtIndexPath:ToIndexPath: to support user reordering of the rows. One of these tableViews works perfectly. The other one, however, gets confused and starts displaying the same subview no matter which row is selected - i.e. its row indexing seems to have got messed up.
I've temporarily fixed this by adding a [tableView reloadData] at the end of theMoveRowAtIndexPath method, but I don't understand why it wasn't working in the first place - especially since another view with the exact same code works perfectly. Obviously, there must be another method in this view controller which is messing it up, but I don't know where to look.
Here is the code that is the same in both:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
userDrivenDataModelChange = YES;
NSMutableArray *things = [[fetchedResultsController fetchedObjects] mutableCopy];
NSManagedObject *thing = [[self fetchedResultsController] objectAtIndexPath:fromIndexPath];
[things removeObject:thing];
[things insertObject:thing atIndex:[toIndexPath row]];
int i = 0;
for (NSManagedObject *mo in things)
{
[mo setValue:[NSNumber numberWithInt:i++] forKey:#"displayOrder"];
}
[things release], things = nil;
[managedObjectContext save:nil];
userDrivenDataModelChange = NO;
}
(For what it's worth, the one that works is the child view of the one that doesn't, and they are in a to-many Core Data relationship).

Use a different identifier for each tableviews in
(UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
If you use the same identifier string, they will pickup each other's cached cell objects and show inconsistent data.

I forgot that I was using a custom cell on the parent view, and that the user presses a UIButton within the cell, rather than the cell itself. The custom cell has its index path set as a property by tableView:cellForRowAtIndexPath:, which of course never gets called again after the table cells are moved. Hence, I had to add a [tableView reloadData] to make sure the cells get updated.

Related

How to prevent TableViewCell from duplicating during recycle?

So my situation is pretty unique. I have a to-do list app with a bunch of tasks. Each task has a UITableViewCell. After each table view cell is tapped, it creates a view controller with the task at that row's index path's property. These view controllers are all stored in a NSDictionary. This is the code representation of what I just said:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
}
So this method of creating unique view controllers and storing them with a certain key almost always works. The problem arises when I delete or move the view controllers:
I was under the impression that a cell's index path gets recycled as you scroll down (dequeue). Doesn't that mean marking each cell with a number identifier would result in multiple cells for the same identifier?
Also, if you stored each view controller with a indexPath key, how do you make sure the key isn't set to two view controllers..? For example. Let's say you have 4 cells, which means 4 view controllers. You delete cell 3. Cell 4 moves down to cell 3s spot. You create a new cell which goes to spot 4. Now you have two controllers with the same indexPath key! How do you avoid this?? It's screwing up my app right now because tasks that have already been moved are loading their properties in the wrong view controller/cell!
I was suggested this to solve the problem before: "You maintain an NSMutableArray that "shadows" the contents of the table." However, I don't understand what this means/how to implement it.
You can use a technique we used to use on old databases. You store an NSInteger as a class var, and use that to assign a unique id to each of the cells as you create them. As you create each cell, you increment the unique id. Like this:
in your interface:
#property (nonatomic, assign) NSUInteger nextUniqueId;
then in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
cell.tag = self.nextUniqueId++;
and then track those tags in your viewControllers. Just give them an assignable property, or customize the init to include the id.
Better to set the tag value for each row in cell for row at index path method.Store that tag value globally,and use that tag in did select row method.

All items of a UITableView disappear after deleting only one of them

Looks a trivial task, however I cannot find what I am doing wrong. I have a table view with sections and I am able to delete 1 item, but when I try to delete more than one item all items disappear from the table.
It is important to mention here that the scenario works well if I delete only 1 row. Deleting one row does not impact the items of the UITableView.
When I click the edit button, every row is candidate for deletion. Code is
[self.tableView setEditing:YES animated:YES];
When I am deleting a row the code calls:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *itemsOfGroup = [[table1 getItems] retain];
int section = indexPath.section;
section = section - 1;
MyItem *deleteItem = (MyItem*)[[[sections objectAtIndex:section] objectForKey:#"SectionEntries"] objectAtIndex:indexPath.row];
[itemsOfGroup removeObject:deleteItem];
[table1 setItems:[itemsOfGroup autorelease]];
[[[sections objectAtIndex:section] objectForKey:#"SectionEntries"] removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject: indexPath] withRowAnimation:UITableViewRowAnimationRight];
[self createSections];
[self.tableView reloadData];
}
Is it possible to have a problem with my memory? (I think once the message "message sent to deallocated instance" appeared for an NSArray class)
Important to mention also that my items are not actually deleted from the data source. They only disappear from the table view.
Why are you autoreleasing itemsOfGroup when you've just set the Items in table 1 to it? Should be a release call after the set, or better to use the self... mechanism with properties. Check your memory allocations/deletions here. Are you throwing away all your content? Also, after your delete, what is your numberOfRowsInSection: and numberOfSections: returning? check those.

Using a Table Cell's Text to Access Corresponding Object

Currently, I have a savedWorkout class that is simply a Table View populated with different exercises in each cell. My goal now is for the user to be able to click on each individual exercise, which will take you to a new view filled with detailed information about it.
For this, I have created an Exercise class that will hold the detailed information about the new object. Is this possible?
Here is some pseudo-code I have written up:
if (Table View Cell's Text == ExerciseObject.exerciseName) {
Populate a view with the corresponding information;
}
Being new to iPhone programming, I'm not exactly sure what would be the best way to do this, and this is what i'm thinking would be the best way to go about it.
My Exercise class holds an NSString to keep track of the exercise name, and three NSMutableArray's to hold different information.
Please let me know if I am going in the right direction.
EDIT:
After trying to implement my pseudo-code this is what I came up with:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Exercise *exerciseView = [[Exercise alloc] initWithNibName:#"Exercise" bundle:nil]; //Makes new exercise object.
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *str = cell.textLabel.text; // Retrieves the string of the selected cell.
exerciseView.exerciseName.text = str;
[self presentModalViewController:exerciseView animated:YES];
}
However, this doesn't seem to work. When the new view is presented, the label doesn't show up (I connected the UILabel exerciseName to my desired string). Am I implementing this wrong?
Yes, of course it's possible. Just use the delegate method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
and check your data source cell based on the index location.
You might need to post your cellForRowAtIndexPath method. If done traditionally, it uses the indexPath.row to access an array of exercises to get a particular exercise, then changes cell properties based on the particular exercise. Is that about right?
It so, then you're halfway home.
EDIT
1) Use the code inside your cellForRowAtIndex path to init your str, as indicated here.
2) The new view controller view hasn't been built yet. You can't initialize a subview in the view hierarchy before the VC is ready. You need to pass the string to a property in that view controller (in a custom init method if you want), then on that class's viewDidLoad, you can set the exerciseName field to the string property you saved earlier. That subview shouldn't be part of the classes public interface.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// There should be an array of exercises, the same one used in cellForRowAtIndexPath:
NSString *str = [self.myArrayOfExercises objectAtIndex:indexPath.row];
// Just made code up here, but however you get a string to place in the cell
// in cellForRowAtIndexPath do that same thing here.
Exercise *exerciseView = [[Exercise alloc] initWithNibName:#"Exercise" bundle:nil];
// Might be wise to rename this ExerciseViewController, since it's probably (hopefully) a ViewController subclass
// no need to get a table cell, you have the info you need from your exercise array
//UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//NSString *str = cell.textLabel.text; // Retrieves the string of the selected cell.
exerciseView.exerciseName.text = str;
[self presentModalViewController:exerciseView animated:YES];
}

UITableView with NSMutableArray

I’m populating a UITableView based on the values of an NSMutableArray. This table view has search results. If the user clicks in one of the results, one will navigate to another screen. If the user clicks “back”, the search results are filled in again. At this point, while the table view is being repopulated, the old values still appear, just as I want. However, since I’m doing:
- (void)viewWillAppear:(BOOL)animated
{
NSMutableArray *m = [[NSMutableArray alloc] init];
self.searchResultsArray = m;
[m release];
}
The old cells information is no longer available. Thus, the app crashes if the user clicks in one of the old cells or scrolls the UITableView because I’m accessing the mutable array which was reinitialized above.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *cellArray = [searchResultsArray objectAtIndex:indexPath.row];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
appDelegate.selectedCell = [searchResultsArray objectAtIndex:indexPath.row];
}
Do you have any suggestions concerning how should I do this properly?
Thanks.
this is because you are re-initalizing the array each time the view comes back in focus, the array will then have 0 objects in, causing the issue when you select a row and reference an index in the array that simply was wiped when the viewWillAppear is called.
why not init ' self.searchResultsArray ' in viewDidLoad (remembering to undo this with release when the device receives memory warning)
let me know how you get on.
You should reset the array only when the view loads:
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *m = [[[NSMutableArray alloc] init] mutableCopy];
self.searchResultsArray = m;
[m release];
}
You should always call the reloadData: method when changing the data in the UITableViewDataSourceDelegate instance.
Let me know if this helps
Please try below code:
- (void)viewWillAppear:(BOOL)animated
{
// Your search result array with your old values
// Now add or appened new data to your old search results.
[self.searchResultsArray addObject:#"Your Value 1"];
[self.searchResultsArray addObject:#"Your Value 2"];
[self.searchResultsArray addObject:#"Your Value 3"];
[self.tableView reloadData];
// if you need to add new values from a array put this code in a loop.
}
It should helps you...
Thx
It seems to me that you are not initialising the NSArray in the right method (viewDidLoad:).
Using XLData you don't have to care about where you set up the storage, reload the UITableView or add items to the NSArray (searchResultsArray) since it keeps track of the data (NSArray) and updates the UITableView accordingly and on the fly.

UITableView won't update properly (even with call to reloadData)

I am producing an iPhone app for which part of the interface is exactly like the 'Most Popular' section of the iPhone YouTube app.
This 'popular' section is accessed from a Tab Bar at the bottom and the navigation bar at the top contains a UISegmentedControl to select 'Today, This Week, Month etc..'
Because most of the app consists of UITableViews with cells containing very similarly structured content, I have created a common MyAppTableViewController which inherits UITableViewController. My 'popular' section thus consists of a PopularTableViewController which inherits MyAppTableViewController. The actual UITableView resides within MyAppTableViewController.
PopularTableViewController has the method:
- (void) segmentChangeTimeframe:(id)sender {
UISegmentedControl *segCtl = sender;
if( [segCtl selectedSegmentIndex] == 0 )
{
// Call [self parse-xml-method-which-resides-in-MyAppTableViewController]
}
//... ... ...
}
The MyAppTableViewController makes use of NSXMLParser and thus has the code:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}
(There are other methods which updates the data structure from which the table view gets it's data)
I have put console output code into the xml parsing methods, and when run, selecting the different segments causes the correct xml files to be parsed fine and the data structure seems to contain the correct values.
The problem is that the contents of the table cells wont change! grr! UNLESS!... A cell is scrolled out of view, and then back into view... THEN its changed!
I have done lots of searching about for this problem and one suggestion for a similar problem was to place the [self.myTableView reloadData] into its own method e.g. myReloadDataMethod and then use:
[self performSelectorOnMainThread:#selector(myReloadDataMethod) withObject:nil waitUntilDone:NO];
I tried placing the above code into the parserDidEndDocument method and it made absolutely no difference! I'm absolutely stumped and am wondering if anybody has any idea what's going on here.
Update:
The code to populate the cells is done with:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:MyIdentifier] autorelease];
}
// Set up the cell
int itemIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString *artistName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemA"];
NSString *mixName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemB"];
cell.textLabel.text = itemA;
cell.detailTextLabel.text = itemB;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
The above code is in MyAppTableViewController which is also where myItemList resides.
Your -performSelectorOnMainThread: code is for when you make changes to the model classes on a background thread. UI events (including -reloadData) need to occur on the main thread. If you're not using a background thread, then this is unnecessary. If you are, something like it is mandatory.
If you are changing the value of a specific cell, the way you achieve that is to change the cell itself. On iPhone, cells are full views (unlike on Mac), so if you want to change their data, you just change their data and call -setNeedsDisplay. You can get the cell (view) for a given location using -cellForRowAtIndexPath:. You can determine if a given cell is onscreen by using -indexPathsForVisibleRows or -visibleCells.
It is very rare to need to call -reloadData. You should only do that if you are throwing away everything and loading completely different data. Instead, you should use the insertion/deletion routines to add/remove rows, and you should just update the views of existing rows when their data change.
I had this same problem, and it was because I had a [tableView beginUpdates] call without an endUpdates call after.
Have you tried [tableView setNeedsDisplay:YES]?
After calling -reloadData, do you recieve callback to tableView:numberOfRowsInSection: ?
I'm almost sure, that self.myTableView is nil here:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}