Automatic adjustment of UIScrollView content offset with custom UIControl

1.1k views Asked by At

When a UITextField is added to a UIScrollview the scroll view automatically adjusts its contentOffset so that the view will not be obscured by the keyboard.

I have a custom UIControl which also presents a keyboard when it becomes the first responder by assigning its inputView property. The same scrolling behavior does not work. Is there a way to configure a UIControl such that a scroll view will keep it visible when the keyboard is presented?

My guess is that it could be possible by overriding a property defined in one of the protocols UITextField and other classes which this behavior conform to. But these can be a bit of a maze. Also note, the issue here has nothing to do with the scroll view's contentInset property. The scroll view can scroll to show the custom control, it just doesn't do it automatically when the control becomes the first responder.

2

There are 2 answers

1
markflowers On BEST ANSWER

It looks like this is handled by an internal private method that Apple utilizes [UIFieldEditor scrollSelectionToVisible] as noted on this blog: http://sugarrushva.my03.com/712423-disable-uiscrollview-scrolling-when-uitextfield-becomes-first-responder.html

It appears to do this by stepping back up through the view hierarchy and if it finds a parent UIScrollView, it scrolls the view to bring the UITextField into visible view. You'll need to implement the scrolling manually on your custom control when it becomes first responder, or handle it by introspecting the parent views.

0
Anthony Mattox On

I was pointed in the right direction by @markflowers.

Based on that, here's what I've written into the control to get the desired behavior:

- (BOOL)becomeFirstResponder {
    if ([super becomeFirstResponder]) {
        [self scrollParentViewToFrame];
        return YES;
    }
    return NO;
}

- (void)scrollParentViewToFrame {
    UIScrollView *scrollView = self.parentScrollView;
    CGRect frame = [scrollView convertRect:self.bounds fromView:self];
    [self.parentScrollView scrollRectToVisible:frame animated:YES];
}

- (UIScrollView *)parentScrollView {
    return (UIScrollView *) [self closestParentWithClass:[UIScrollView class]];
}

Note that the frame attribute is not used in case the control is not a direct descendant of the scroll view. Instead convert the bounds to the scroll view's coordinate space.

The scroll adjustment is also needs to be performed after [super becomeFirstResponder] is called for it to interact properly with keyboard notifications that are being used to adjust the insets of the scroll view.

I defined the method to search for the closest parent scroll view in a UIView category which made it easier to recursively search up the hierarchy.

- (UIView *)closestParentWithClass:(Class)class {
    if ([self isKindOfClass:class]) {
        return self;
    }
    // Recursively searches up the view hierarchy, returns nil if a view
    // has no superview.
    return [self.superview closestParentWithClass:class];
}