简体   繁体   中英

UIKit state preservation not restoring scroll offset

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM