soulshined
soulshined

Reputation: 10592

NSIndexPath for current tableview position

I'm currently trying to emulate a websites anchor tags to quickly navigate to a specific point on the browser page. I have a Segmented Control to act as the hyperlink, if you will. See picture below:

image1

As my tableview sections and rows grow this will come in handy as a quick tool for users to utilize, especially for smaller devices. However i'm running into a pickle with equating the scroll points with a sections first cell position, but I'd rather if possible get the section titles position.

My goal is to update the segmentControl.selectedSegmentIndex on tap (easy part) as well as when scrolling (pickle part) so it's a more fluid UI interaction.

I've set up my selectedSegmentIndex to scroll to specific sections on tap with these NSIndexPaths with an IBAction:

NSIndexPath *aboutIndex = [NSIndexPath indexPathForRow:0 inSection:0];
NSIndexPath *contactIndex = [NSIndexPath indexPathForRow:0 inSection:1];
NSIndexPath *publicationsIndex = [NSIndexPath indexPathForRow:0 inSection:2];
NSIndexPath *settingsIndex = [NSIndexPath indexPathForRow:0 inSection:3];

switch (self.segmentControl.selectedSegmentIndex)
{
    case 0: [self.tableView scrollToRowAtIndexPath:aboutIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
        break;
    //etc etc
    ...

Using the same code I placed it in the scrollViewDidScroll: method just like you'd expect, but i'm having a hard time getting the specific indexPath of the current tableView scroll point. I first tried this as well as [self.tableView indexPathsForVisibleRows]:

 -(void)scrollViewDidScroll:(UIScrollView *)scrollView {

    NSIndexPath *currentIndex = [self.tableView indexPathForRowAtPoint:CGPointMake(0, self.tableView.contentOffset.y)];
    NSLog(@"current is %@", currentIndex);

    //And then changing the selectedSegmentIndex on scroll:

    if ((currentIndex >= aboutIndex) && (currentIndex < contactIndex)) {
        self.segmentControl.selectedSegmentIndex = 0;
    // other else if's
    ...
}

That works sometimes, when it wants to basically, and only when scrolling down, I get null when manually scrolling up. So I changed it to:

if ([currentIndex isEqual:aboutIndex]) {
    self.segmentControl.selectedSegmentIndex = 0;
} else if ([currentIndex isEqual:contactIndex]) {
    self.segmentControl.selectedSegmentIndex = 1;
} else if ([currentIndex isEqual:publicationsIndex]) {
    self.segmentControl.selectedSegmentIndex = 2;
} else if ([currentIndex isEqual:settingsIndex]) {
    self.segmentControl.selectedSegmentIndex = 3;
}

Which works all the time, except when scrolling up. Why am I getting null scrolling up?

Upvotes: 0

Views: 612

Answers (2)

soulshined
soulshined

Reputation: 10592

After a lot of playing around with this, I really wanted this to be a polished & fluid look, I've found a better solution as opposed to jn_pdx. Although his answer was resourceful and in the right direction, I did try it that way, and created CGRects for the sections but the only viable way I found to scroll to the - (CGRect)rectForSection:(NSInteger)section was [self.tableView scrollRectToVisible:animated:] , when performing it this way the rect literally scrolls until it's visible, so when scrolling down it works because it brings the top most part of the tableView to where it is visible which in this case happens to be the top of the screen, but when scrolling it up, it scrolls the rect until it's visible, which ends up at the bottom of the screen (not desired effect), because that's the first point it becomes visible. So instead, I decided to keep the NSIndexPath methods and reference against that. I accomplished this by implementing the following

For tap on segmentedControl:

- (IBAction)segmentIndexChanged:(id)sender {

//Created the NSIndexPaths for all the sections 1st cells
NSIndexPath *aboutIndex = [NSIndexPath indexPathForRow:0 inSection:0];
NSIndexPath *contactIndex = [NSIndexPath indexPathForRow:0 inSection:1];
NSIndexPath *publicationsIndex = [NSIndexPath indexPathForRow:0 inSection:2];
NSIndexPath *settingsIndex = [NSIndexPath indexPathForRow:0 inSection:3];

    //Scroll to the top most part of the cell
    switch (self.segmentControl.selectedSegmentIndex)
    {
    case 0:
        [self.tableView scrollToRowAtIndexPath:aboutIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
        break;
    case 1:
        [self.tableView scrollToRowAtIndexPath:contactIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
        break;
    case 2:
        [self.tableView scrollToRowAtIndexPath:publicationsIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
        break;
    case 3:
        [self.tableView scrollToRowAtIndexPath:settingsIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
        break;
    default:
        break; 
    } 
}

Then when the UITableView gets manually scrolled I update the selectedSegmentIndex by directly correlating to the NSIndexPath it scrolls to when tapped.

For scrolling UITableView:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {

//Created a rect correlating to the tapped Index Paths
CGRect aboutRect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
CGRect contactRect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]];
CGRect publicationsRect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:2]];
CGRect settingsRect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:3]];
//Get the current table view contentOffset point in CGRect form instead of CGFloat
CGRect scrollY = CGRectMake(0, self.tableView.contentOffset.y, self.tableView.bounds.size.width, self.tableView.bounds.size.height);
// NSLog current y point
NSLog(@"scrollY %@", NSStringFromCGRect(scrollY));

//Catch the intersecting points with CGRectContainsRect not CGRectEqualToRect
    if (CGRectContainsRect(scrollY, aboutRect)) {
        self.segmentControl.selectedSegmentIndex = 0;
    } else if (CGRectContainsRect(scrollY, contactRect)) {
        self.segmentControl.selectedSegmentIndex = 1;
    } else if (CGRectContainsRect(scrollY, publicationsRect)) {
        self.segmentControl.selectedSegmentIndex = 2;
    } else if (CGRectContainsRect(scrollY, settingsRect)) {
        self.segmentControl.selectedSegmentIndex = 3;
    }
}

And it works flawlessly & consistently

Upvotes: 0

jnpdx
jnpdx

Reputation: 52347

To answer your last question ("does anyone know how to get a section title's origins?"), UITableView has a method - (CGRect)rectForSection:(NSInteger)section that will return a CGRect of the section. See documentation: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/index.html

Since the title should be at the top of the section, you should be able to use the origin point of the rect as the origin point of your title (may have to account for the text inset, of course)

Alternately, you could probably also use - (CGRect)rectForHeaderInSection:(NSInteger)section, since the top coordinate should be the same on either.

And, of course, you can compare it to the scroll position to get the relative screen position of those CGRects.

Upvotes: 1

Related Questions