UIKit state preservation not restoring scroll offset - iphone

I have an app that is using UIKit state preservation in iOS 6. I am able to save/restore the state of the view controllers, ie which tab is selected and navigation controller hierarchy, however I cannot get my table view to restore it's offset. I have a restoration identifier in my storyboard for the view as well as the view controller and the view controller (the table's data source) implements UIDataSourceModelAssociation as follows:
- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view
{
TSStatus *status = [self._fetchedResultsController objectAtIndexPath:indexPath];
return status.objectID.URIRepresentation.absoluteString;
}
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view
{
NSURL *statusURL = [NSURL URLWithString:identifier];
NSManagedObjectID *statusID = [[TSDataController sharedController].persistentStoreCoordinator managedObjectIDForURIRepresentation:statusURL];
TSStatus *status = (TSStatus *)[[TSDataController sharedController].mainContext objectWithID:statusID];
return [__fetchedResultsController indexPathForObject:status];
}
modelIdentifierForElementAtIndexPath:inView: is getting called when the app goes into the background, however modelIdentifierForElementAtIndexPath:inView: never gets called.

This isn't a real answer to your question, but I've not been able to get a table view restore its contentOffset, either.
I guess this is a bug in iOS 6, because the documentation clearly states that a UITableView restores its contentOffset, when 1) it has a restorationIdentifier 2) the view controller the view belongs to has a restorationIdentifier and 3) the data source conforms to the UIDataSourceModelAssociation protocol.
You can restore the contentOffset and the selected item manually in your view controller, though:
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeObject:[NSValue valueWithCGPoint:self.tableView.contentOffset] forKey:#"tableView.contentOffset"];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
if (indexPath != nil) {
NSString *modelIdentifier = [self modelIdentifierForElementAtIndexPath:indexPath inView:self.tableView];
[coder encodeObject:modelIdentifier forKey:#"tableView.selectedModelIdentifier"];
}
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
[super decodeRestorableStateWithCoder:coder];
CGPoint contentOffset = [[coder decodeObjectForKey:#"tableView.contentOffset"] CGPointValue];
self.tableView.contentOffset = contentOffset;
NSString *modelIdentifier = [coder decodeObjectForKey:#"tableView.selectedModelIdentifier"];
if (modelIdentifier != nil) {
NSIndexPath *indexPath = [self indexPathForElementWithModelIdentifier:modelIdentifier inView:self.tableView];
if (indexPath != nil) {
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
}
I have no idea why UITableView doesn't do that automatically, even though the documentation says it does. If someone knows the answer, please comment.

I've found this can work, if the UITableView also has a restorationIdentifier set.
However it doesn't work if the UITableViewController is inside a UINavigationController. This has been reported to Apple, Problem ID: 13536778. This problem seems to occur on both iOS 6.0 and 6.1.3.

This is a bug in iOS 6.
To get state restoration for your table view working using the UIDataSourceModelAssociation protocol you should call -reloadData on your table view before returning the valid index path in -indexPathForElementWithModelIdentifier:inView: like so:
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view
{
NSURL *statusURL = [NSURL URLWithString:identifier];
NSManagedObjectID *statusID = [[TSDataController sharedController].persistentStoreCoordinator managedObjectIDForURIRepresentation:statusURL];
TSStatus *status = (TSStatus *)[[TSDataController sharedController].mainContext objectWithID:statusID];
[self.tableView reloadData];
return [__fetchedResultsController indexPathForObject:status];
}

See Apple's State restoration sample for how to achieve this. The magic fix happens in the decodeRestorableStateWithCoder method with a call to reloadData:
MyTableViewController.m
// this is called when the app is re-launched
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
// important: don't affect our views just yet, we might not visible or we aren't the current
// view controller, save off our ivars and restore our text view in viewWillAppear
//
NSLog(#"MyTableViewController: decodeRestorableStateWithCoder");
[super decodeRestorableStateWithCoder:coder];
self.tableView.editing = [coder decodeBoolForKey:kUnsavedEditStateKey];
[self.tableView reloadData];
}
Note it is odd that they encode the editing state because editing is ended by their enter background notification handler before preservation begins so it will always restore not editing. Also they try to set self.tableView.editing instead of self.editing so the edit button wouldn't update. Also note the comment about not affecting the views which is odd firstly given they do affect the views and second viewWillAppear is called before decoding state. Given these mistakes I wouldn't use this example to tune your programming skills.
The other answer states to reload in the indexPathForElementWithModelIdentifier which is not a good idea given it is called multiple times (at least twice) to find various index paths of visible and selected objects.

Related

UITableView correcting scroll position after device rotation

I'm building something like a reader for a book. When the user rotates the phone I want to increase the font size. I'm using a UITableView to display chunks of text.
Problem is that, increasing the font size increases height of rows in my table view and if I was reading paragraph 320 in portrait mode I get 280 or something similar in landscape mode.
I have set up a rotation notification listener using this code:
UIDevice *device = [UIDevice currentDevice];
[device beginGeneratingDeviceOrientationNotifications];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:device];
and tried to save the last paragraph index before rotation and then scroll down to it after the rotation but I can't seem to achieve desired effect.
What's the best way to handle this kind of situation and where do I actually implement "before" and "after" states of rotation?
I'd like it to work on iOS 4+.
Swift 3 version of Said Ali Samed's answer (willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated):
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: { context in
// Save the visible row position
self.visibleRows = self.tableView.indexPathsForVisibleRows!
context.viewController(forKey: UITransitionContextViewControllerKey.from)
}, completion: { context in
// Scroll to the saved position prior to screen rotate
self.tableView.scrollToRow(at: self.visibleRows[0], at: .top, animated: false)
})
}
Use delegate method willRotateToInterfaceOrientation: to store the visible cell in an array then using the other delegate method of UITableView didRotateFromInterfaceOrientation: scroll to the visible index path that you stored earlier in the array. This is recommended and you don't have to rely on the inconsistent 0.2 seconds wait in a different thread to handle post rotate event.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// Save the visible row position
visibleRows = [tableview indexPathsForVisibleRows];
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
// Scroll to the saved position prior to screen rotate
[tableView scrollToRowAtIndexPath:[visibleRows objectAtIndex:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
}
Since I couldn't get a good answer I'll answer myself. I've looked everywhere but couldn't find a way to do what I wanted so I just used the shouldAutorotateToInterfaceOrientation method to increase the size of font and then start a little thread that sleeps for 0.2 seconds and after that scrolls to the desired row. Thanks for your help.
Edit: Use delegate method willRotateToInterfaceOrientation: to store the visible cell in an array then use the delegate method didRotateFromInterfaceOrientation: to scroll to the visible index path that you recorded in the array.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// Save the visible row position
visibleRows = [tableview indexPathsForVisibleRows];
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
// Scroll to the saved position prior to screen rotate
[tableView scrollToRowAtIndexPath:[visibleRows objectAtIndex:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
}
A simple way to do it would be to store the index path of the top visible cell, change the font size then restore the top cell:
NSIndexPath* topCellIndexPath = [[_tableView indexPathsForVisibleRows] objectAtIndex:0];
//Insert code to change font size here
[_tableView scrollToRowAtIndexPath:topCellIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
This code can be put in any method that's run when orientation changes, such as the orientationChanged: method you put in the question.
This will not take into account having scrolled halfway down a cell so if the height of your cells is large it will not work well and a more complicated method using content offsets would be needed. Let me know if this is the case.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)
fromInterfaceOrientation
{
NSLog(#"didRotateFromInterfaceOrientation:%d",fromInterfaceOrientation);
[tableView reloadRowsAtIndexPaths:[tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationNone];
}
Then I would recommend adding either the interfaceOrientation number or simply the table width to the dequeue cell name that way the tableView knows that cells in one rotation are different from those in another. Like so:
- (UITableViewCell *)tableView:(UITableView *)tv
cellForRowAtIndexPath:(NSIndexPath *)indexPath
withType:(NSString *)s_type
{
UITableViewCell *cell = nil;
// add width of table to the name so that rotations will change the cell dequeue names
s_cell = [s_cell stringByAppendingString:
[NSString stringWithFormat:#"%#%d",#"Width",(int)tv.bounds.size.width]
];
NSLog(#"%#",s_cell);
cell = [tv dequeueReusableCellWithIdentifier:s_cell];
if( cell == nil ) {
cell = [[[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:s_cell] autorelease];
}
}
Firstly, to reload all of your table cells use [self.tableView reloadData]
Secondly, add the line of code that is responsible for the shrinking inside the (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method.
Example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Some identifier and recycling stuff
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) {
//Make labels smaller
}
else {
//Make them bigger
}
}
Or you can just call your updateCellForRotate:forRow: method when making them. But I'm not sure how that function works, so I can't be too specific.

UITable cell selection in a SplitViewController

I have a UISplitViewController with a Table View for navigation. It's similar to the Mail app. When you click on a table view in portrait mode, the popup hides itself. When you click on the nav bar to get the popup back, the selected item no longer appears selected. How can make this item appear selected without re-selecting the item? (just like in the mail app)
In your viewDidLoad method, do you call
self.clearsSelectionOnViewWillAppear = NO; ?
This is how Xcode's SplitView template does it.
Do you have by any change a
[tableView deselectRowAtIndexPath:indexPath animated:YES];
in your didSelectRowAtIndexPath in the RootViewController ?
I've got a solution that works, but it's frustratingly hacky. I have to call selectRowAtIndexPath twice. It seems that cellForRowAtIndexPath is invalidating the selection made in viewWillAppear. It still needs to be called in viewDidAppear, however, so the view scrolls to the proper position before cellForRowAtIndexPath is called.
- (void)viewWillDisappear:(BOOL)animated
{
NSIndexPath *selected = [self.tableView indexPathForSelectedRow];
_selectedRow = selected.row;
}
- (void)viewDidAppear:(BOOL)animated
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_selectedRow inSection:0];
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//initialize cell code here...
if (indexPath.row == _selectedRow) {
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle];
}
}
For your table view controller, is -viewWillAppear: called before the pop-up is displayed? If so, you could write it as so:
- (void)viewWillAppear:(BOOL)animated
{
[self.tableView selectRowAtIndexPath:<indexPath>
animated:animated
scrollPosition:UITableViewScrollPositionMiddle];
[super viewWillAppear:animated];
}
Obviously, replace <indexPath> with the proper index path and set the scroll position how you want it. You may also want to pass NO instead of animated to make it appear like it was selectd before the view appeared.

cellForRowAtIndexPath not called; sections returns 1 and rows returns 4

After parsing JSON data in a Data class, I set the UIViewController's NSArray *headlines property in a fillArrays method of the same Data class. In the viewDidAppear method of my UIViewController, I call reloadData on my UITableView. numberOfSectionsInTableView fires and returns 1, then numberOfRowsInSection fires and returns an array count of 4 (for 4 strings in the array). However, control never gets to cellForRowAtIndexPath and I'm having the hardest time understanding why, especially since I have valid sections and rows. The cells are all visible.
I've added the UITableViewDataSource and UITableViewDelegate protocols to the UIViewController interface and set the UITableView's delegate and dataSource to self in viewDidLoad (which also is verified by the row and section count methods being called).
I'm wondering if it has something to with me reinitializing the UIViewController in Data.m in order to set its properties.
In Data.m:
- (void)fillArrays:(NSArray *)jsonObjs {
NSLog(#"fillArrays");
HeadlinesRootViewController *hrvc = [[HeadlinesRootViewController alloc] init];
hrvc.headlines = [self getJsonValuesForKey:#"headline" inArrayOfObjects:jsonObjs];
[hrvc viewDidAppear:NO];
}
In ViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad");
// Table view
headlineTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 180, self.view.bounds.size.width, 300) style:UITableViewStylePlain];
[headlineTableView setDelegate:self];
[headlineTableView setDataSource:self];
// Temporary
self.headlines = [[NSMutableArray alloc] initWithObjects:#"headline1", #"headline2", #"headline3", #"headline4", nil];
[self.view addSubview:headlineTableView];
self.headlineTableView = headlineTableView;
[headlineTableView release];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"viewdidappear");
NSLog(#"headlines: %#", self.headlines); // Returns an array of 4 headlines
if( [self.headlines count] != 0 ){
[self.headlineTableView reloadData];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"numberOfSectionsInTableView: 1");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"numberOfRowsInSection: %d", [self.headlines count]);
return [self.headlines count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
cell.text = [[NSString alloc] initWithFormat:#"%#", [self.headlines objectAtIndex:indexPath.row]];
return cell;
}
In fillArrays, you create another view controller - but you never do anything with it or its view, you would never see that view. You would never call viewDidAppear manually either, that happens automatically when a view controllers view is displayed (ONLY in the context of a navigation controller though).
Normally the flow is, you create a view controller and either add that view as a subview of a current view, or push it as a new window via a navigation controller. I'm pretty sure your whole issue is that they table is never added to a view anyone actually sees, so the table calls the other methods but never calls cellForRow because its layoutSubviews code is simply not being called.
Have you added your tableView to the view of UIViewController?
It happened to me, and when I added this
[self.view addSubview:table];
[table release];
then cellForRowAtIndexPath started working.
For Google's sake:
If tableView:numberOfRowsInSection returns zero for whatever reason tableView:cellForRowAtIndexPath will not get called because there are no rows to call it for.
Check to make sure that the tableView delegate and dataSource are pointed to the viewController
I cannot see anything wrong with the code as-is, have you verified with breakpoints that cellForRow is never reached (even though I see you have a log statement)?
Also I would try just for a sanity check to return "1" explicitly in rowsInSection, and hardcode a string in the cell you are returning in cellForRow.
If all else fails, create a new table view controller from the XCode templates and put your calls in there - then when that works, work backwards to why your code does not.
Also, it would be good to see your viewDidLoad setup code (add to answer above please).
if you're setting the delegate and datasource at viewDidLoad, then that may be the source of your bug. Can you set the datasource and delegate in init?
I'm not sure that you add your UITableView as subview to UIViewController.view. This was my approach anyway.
In this approach, I found execution did not get into cellForRowAtIndexPath until I sent UIViewController.view to the back after adding UITableView as subview.
Getting this far was only part of the problem. At this point, it seemed that my other view controllers no longer respond to touch events. I found that when I also add the UITableView as a subview to the rootViewController, all my views got the appropriate touch events.
Thank you so much pxl. When I move the UITableView initialization from viewDidLoad to:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil"
it works perfectly when ever I make delete or update some rows the UITableView gets reloded to my UIView.
Swift version
Add self.table.layoutIfNeeded() and then self.tableView.reloadData()

Refreshing the UITableView from UITableViewCell

Is there an easy way to trigger a [UITableView reloadData] from within a UITableViewCell? I am loading remote images to be displayed after the initial table display, and updating the image with self.image = newImage does not refresh the table. Resetting the cell's text value does refresh the table, but this seems sloppy.
MyTableViewCell.h
#interface MyTableViewCell : UITableViewCell {}
- (void)imageWasLoaded:(ImageData *) newImage;
MyTableViewCell.m
#implementation MyTableViewCell
- (void)imageWasLoaded:(UIImage *) newImageData {
self.image = newImage; //does not refresh table
//would like to call [self.tableView reloadData] here,
//but self.tableView does not exist.
//instead I use the below line
self.text = self.text; //does refresh table
}
#end
I did the exact thing that you're trying to do. The thing you're looking for is needsLayout. To wit (this is a notification observer on my UITableViewCell subclass):
- (void)reloadImage:(NSNotification *)notification
{
UIImage *image = [[SSImageManager sharedImageManager] getImage:[[notification userInfo] objectForKey:#"imageUrl"];
[self setImage:image];
[self setNeedsLayout];
}
This will pop in your image without having to reload the entire table, which can get very expensive.
Get a reference to the containing UITableView using the superview property. Then tell it to "reloadData":
UITableView *parentTable = (UITableView *)self.superview;
[parentTable reloadData];
It is less expensive to only reload the sections/rows that need reloading. In iPhone SDK 3.0 there are some methods for doing just that.
Hmm. So imageWasLoaded: is a delegate method called by some asynchronous image loading code? Actually it seems weird that setting self.image does not update the image. Have you tried to add your own UIImageView to the cell rather than using the image property? Might be kind of a hack, but this way the image should update right away (without you having to reload the whole table view, which is definitely not desireable).
Even better: If you are using SDK 3.0, you can use the new imageView property in UITableViewCell (I have not tried this, though):
self.imageView.image = newImage;
Actually, I needed to do exactly the same some weeks ago. However, my approach was to subclass UIImageView and do all the asynchronous image loading/updating in that class. The main reason I did it this way was that I wanted to have a generic and reusable component which can be used in different table view cells (or elsewhere).
CodeGrue is right. After days thinking why [subclassOjb.tableView reloadData] wasn't working I gave up trying to understand that and settled for the only two methods I know works and it's very easy to implement: 1- Notification Center (from tableviewcell you shouldn't use this one) 2- or use the superview to get a handle on your tableview property (this is perfect for uitableviewcells).
Note that using the superview method will work on your uitableviewcell class only, chances are if you have a custom cell you will have a uitableviewcell class which the superview is your tableviewcontroller or whateverClass you have with a uitableview delegate + datasource.
Now if you need to reload your tableview from another class, a datasource class let's say a singleton or whatever; you should use a block within your tableview class to load the data on a different thread, with an inner block to reload the table on the mainthread.
//handle tablview to show spinner (you have to lower your tableview out of the way yourself if you are calling this method)
[self.tableView setContentOffset:CGPointMake(0, -rf.frame.size.height) animated:YES];
//start spinner animation here
[rf beginRefreshing];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
// Load Data from data source class here
[myDataSourceClass fetchDataMethod];
dispatch_async(dispatch_get_main_queue(), ^{
//Reload data and stop spinner
[self.tableView reloadData];
[rf endRefreshing];
//handle tablview to hide spinner atrributed title
[self.tableView setContentOffset:CGPointMake(0, 0) animated:YES];
});
});
If you are on a different view and something happens that you need to reload your table on your table view. Well at that point your table is not on screen there is no reason why you should reload your table if no one is going to see it. Just add [self.tableview reloadDAta] to your viewWillAppear method when users go back to your tableviewcontroller view it will reload the table on the fly.
Anyway for tableviewcell just use the following inside your action to reloadData (credit to CodeGrue)
UITableView *parentTable = (UITableView *)self.superview;
[parentTable reloadData];
**different topic but for those of you starting now. This will save you from possible headaches -
after creating your tableclass and before you even start to deal with it's delegate methods - do this: in your viewDidLoad add the following code:
self.tableView.delegate = self;
self.tableView.dataSource = self;
Anybody that was looking for a way to reload the table from the cell and figured out that CodeGrue's answer does not work, you can still do it this way but you need to double check.
Disclaimer
You should always use a delegate pattern for this, what follows is a hack that might break again in future versions.
Hack to reload the data from the cell
UITableView *parentTable = (UITableView *)self.superview;
if (![parentTable isKindOfClass:[UITableView class]]) {
parentTable = (UITableView *) parentTable.superview;
}
[parentTable reloadData];
try [yourtableview reloadData];
after setting the image to a new image

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];
}