iphoneuiscrollviewfirst-responder

Can I override scroll view's automatic behavior to scroll to the first responder?


I have a UITextField inside a UIScrollView (a few levels deep). I am watching UIKeyboardDidShowNotification, and also calling the same code when I manually change the first responder (I might change to a different text field without momentarily hiding the keyboard). In that code I use scrollRectToVisible:animated: to make sure the UITextField is visible.

I was having a huge headache debugging why that was acting funny, but I realized now that UIScrollView automatically ensures that the first responder is within its bounds. I am changing the frame of the UIScrollView so that none of it is hidden behind the keyboard.

However, my code can be slightly smarter than their code, because I want to show not only the UITextField, but some nearby related views as well. I try to show those views if they will fit; if not whatever, I try to show as much of them as I can but at least ensure that the UITextField is visible. So I want to keep my custom code.

The automatic behavior interferes with my code. What I see is the scroll view gently scroll up so that the bottom edge of my content is visible, then it snaps down to where my code told it to position.

Is there anyway to stop the UIScrollView from doing its default capability of scrolling the first responder into view?

More Info

On reviewing the documentation I read that they advise to change the scroll view's contentInset instead of frame. I changed that and eliminated some unpredictable behavior, but it didn't fix this particular problem.

I don't think posting all the code would necessarily be that useful. But here is the critical call and the values of important properties at that time. I will just write 4-tuples for CGRects; I mean (x, y, width, height).

[scrollView scrollRectToVisible:(116.2, 71.2, 60, 243) animated:YES];

scrollView.bounds == (0, 12, 320, 361)

scrollView.contentInset == UIEdgeInsetsMake(0, 0, 118, 0)

textField.frame == (112.2, 222.6, 24, 24)

converted to coordinates of the immediate subview of scrollView == (134.2, 244.6, 24, 24)

converted to coordinates of scrollView == (134.2, 244.6, 24, 24)

So the scroll view bottom edge is really at y == 243 because of the inset.

The requested rectangle extends to y == 314.2.

The text field extends to y == 268.6.

Both are out of bounds. scrollRectToVisible is trying to fix one of those problems. The standard UIScrollView / UITextField behavior is trying to fix the other. They don't come up with quite the same solution.


Solution

  • I didn't test this particular situation, but I've managed to prevent a scrollview from bouncing at the top and bottom by subclassing the scrollview and overriding setContentOffset: and setContentOffset:animated:. The scrollview calls this at every scroll movement, so I'm fairly certain they will be called when scrolling to the textfield.

    You can use the delegate method textFieldDidBeginEditing: to determine when the scroll is allowed.

    In code:

    - (void)textFieldDidBeginEditing:(UITextField *)textField
    {
        self.blockingTextViewScroll = YES;
    }
    
    -(void)setContentOffset:(CGPoint)contentOffset
    {
        if(self.blockingTextViewScroll)
        {
            self.blockingTextViewScroll = NO;
        }
        else
        {
            [super setContentOffset:contentOffset];
        }
    }
    
    
    -(void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
    {
        if(self.blockingTextViewScroll)
        {
            self.blockingTextViewScroll = NO;
        }
        else
        {
            [super setContentOffset:contentOffset animated:animated];
        }
    }
    

    If your current scroll behaviour works with a setContentOffset: override, just place it inside the else blocks (or preferably, in a method you call from the else blocks).