1

Original Title: Scroll to specific UITableViewCell in a UITableView with dynamic multiple sections

New title: Obtain an NSIndexPath for an "off-screen" UITableViewCell from the index of a data source array

I want to be able to smoothly scroll a table view until a previously selected cell is shown on screen.

The problem arises because I cannot find a solution to determine the indexPath of a cell before it is loaded by tableview:cellForRowAtIndexPath:.

The UITableViewController data source is a NSFetchedResultsController that returns multiple sections.

I have read these SO questions, however they all provide a solution to the problem with only one section:

I have included code below - it works - however the scroll motion is uneven (start/stop) - the code scrolls a row, assesses whether the next row contains the data related to a previously selected cell, and if not scrolls to the next row, and so on and so on... ... until the previously selected cell is found and is on screen.

This code does not work on a device running iOS6.

Anyone have any ideas about how to scroll to the previously selected cell smoothly under iOS 7, and even better if the solution can work under iOS 6.

UPDATE:

  • New question title,
  • thanks to @dasblinkenlight, now working on determining how to obtain an NSIndexPath for an "off-screen" UITableViewCell from the index of a data source array.

Using the enumeration option identified in the first SO question link above.

Updated code to be posted...


Existing Code

Properties.

@property (nonatomic, strong) NSIndexPath *indexPathCurrent;
@property (nonatomic) BOOL selectedCellIsVisibleOnScreen;

The "action" code sits at the end of my tableview:cellForRowAtIndexPath: method.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    <... code to configure cell...>

    BOOL isChecked = NO;

    <... code to set the value for isChecked...>

    if (self.isIOS7 && !self.selectedCellIsVisibleOnScreen) {
        [self setIndexPathCurrent:indexPath];
        [self setSelectedCellIsVisibleOnScreen:isChecked];

        [self.tableView scrollToRowAtIndexPath:self.indexPathCurrent
                              atScrollPosition:UITableViewScrollPositionNone
                                      animated:YES];
    }
}

The property isIOS7 is determined elsewhere and is in place to disable this action for any device running iOS 6, as this code causes hectic behaviour in the table view when executed under iOS 6.

New Code

to be advised...

Community
  • 1
  • 1
andrewbuilder
  • 3,629
  • 2
  • 24
  • 46
  • "The problem arises because I cannot find a solution to determine the indexPath of a cell before it is loaded by tableview:cellForRowAtIndexPath:" <<== This looks like the root cause of the problem. Could you describe the problem in finding that index path before the call of `tableview:cellForRowAtIndexPath`? – Sergey Kalinichenko May 01 '14 at 10:21
  • @dasblinkenlight maybe I have not written my question very well? If my understanding is correct, I am not able to obtain an indexPath reference for my *previously selected cell* until the table view has laid out its sections, rows and cells. An alternative... In the `viewWillAppear` method I could create an array from `fetchedResultsController.fetchedObjects` and enumerate to find the index of the *previously selected cell*, so I guess a better question might then be - how do I cross reference an index within an array with the table view sections and rows to determine an `NSIndexPath`? – andrewbuilder May 01 '14 at 10:41
  • That is a better question indeed. The answer is in the code of your `cellForRowAtIndexPath` method: it is this method that looks up an object in the array when `UITable` gives it an `NSIndexPath`. You need to reverse that calculation - it should be a one-for-one correspondence between index paths and array indexes. When you have one section, the answer is trivial. When there are multiple sections, you need to add up the counts of individual sections, and do a simple loop to decide in what section an array index belongs. – Sergey Kalinichenko May 01 '14 at 10:54
  • Thanks for the pointer @dasblinkenlight, very helpful. I will enjoy digging into this. – andrewbuilder May 01 '14 at 10:59

1 Answers1

0

My answer with thanks to @dasblinkenlight for pointing me in the right direction...

Properties:

// public - receives an array of values for the 'previously selected cells'
@property (nonatomic, strong) NSArray *arrayObjects;            
// private
@property (nonatomic, strong) NSIndexPath *indexPathObjectFromArray;

In viewWillAppear:

    NSManagedObject *selectManagedObjects = nil;
    NSManagedObject *checkManagedObject = nil;
    NSFetchedResultsController *frc = nil;
    NSArray *fetchedObjects = nil;
    NSInteger nofSectionsInTableView;
    NSInteger nofRowsInSection;
    NSInteger currentSection = 0;
    NSInteger currentRow = 0;

    // possible more than one object selected - we want the last or bottom-most object 
    selectManagedObjects = self.arrayObjects.lastObject;

    frc = self.fetchedResultsController;
    fetchedObjects = frc.fetchedObjects;
    nofSectionsInTableView = frc.sections.count;

    while (currentSection < nofSectionsInTableView && self.indexPathObjectFromArray == nil) {
        nofRowsInSection = [[frc.sections objectAtIndex:currentSection] numberOfObjects];
        for (int sectionRow = 0; sectionRow < nofRowsInSection; sectionRow++) {
            checkManagedObject = [fetchedObjects objectAtIndex:currentRow];
            if (checkManagedObject == selectManagedObjects) {
                [self setIndexPathObjectFromArray:[NSIndexPath indexPathForRow:sectionRow
                                                                   inSection:currentSection]];
                break;
            }
            currentRow++;
        }
        currentSection++;
    }

In viewDidAppear:

    [self.tableView scrollToRowAtIndexPath:self.indexPathObjectFromArray
                          atScrollPosition:UITableViewScrollPositionNone
                                  animated:YES];

Note the use of UITableViewScrollPositionNone - this ensures that if the bottom-most previously selected cell is in the initial view (and therefore scrolling is not required), then no scrolling will occur.

andrewbuilder
  • 3,629
  • 2
  • 24
  • 46