Search code examples
iosobjective-cios6uiscrollviewautolayout

Scrollview-Keyboard issue with autolayout


I have screen with multiple TextFields. So main view has scrollview with constraints to edges set to 0. Scrollview has one subview which exactly fits scrollview. And now this subview has all TextFields.

Issue is when navigating between TextFields when user pressed Previous/Next button on toolbar above keyboard. Issue is in iOS 6 only.

It works fine when Next button is pressed. For Previous button, it works for all TextFields except one which is positioned 2nd in row of all TextFields. So what should happen when 5th TextField is active and I keep pressing Previous to go to 2nd TextField, if the TextField is behind NavigationBar or even above that then scrollview's contentOffset should be changed so that TextField comes down and becomes visible. But actually it moves even more upside. Also button at bottom should be placed 20pts from its superview's bottom which is normally the case. But when this issue occurs the button is visible more than 20pts from its superview's bottom. Here, when I scroll even a little bit then scrollview contents shift down sharply and reaches at their correct position.i.e. button is at correct position now.

First image has incorrect position. Second image has correct button position.

enter image description here

enter image description here

Code to navigation between TextFields : Fields are given tag value from 1 to 5.

// Called when previous/next button is pressed
- (void)navigateBetweenTextField:(UISegmentedControl *)segmentedControl
{
    // Check which segment is selected and change textfield accordingly
    UITextField * textFieldToMakeActive = nil;

    // If Previous is pressed
    if (segmentedControl.selectedSegmentIndex == 0)
    {
        textFieldToMakeActive = (UITextField *)[self.view viewWithTag:(self.selectedTextField.tag - 1)];
    }
    // Else if Next is pressed
    else if (segmentedControl.selectedSegmentIndex == 1)
    {
        textFieldToMakeActive = (UITextField *)[self.view viewWithTag:(self.selectedTextField.tag + 1)];
    }

    segmentedControl.selectedSegmentIndex = UISegmentedControlNoSegment;

    // If textfield is not nil
    if (textFieldToMakeActive)
    {
        // Make it active
        [textFieldToMakeActive becomeFirstResponder];
    }
}

Method called when keyboard is about to be visible :

// Called when the UIKeyboardWillShowNotification is sent.
- (void)keyboardWillShow:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    NSTimeInterval animationDuration;
    [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];

    self.keyboardAndAccessoryViewSize = kbSize;        
    [self moveViewSoThatTextFieldIsVisible:kbSize];
}


// It checks if textfield is visible and then moves the view to make it visible if not.
- (void)moveViewSoThatTextFieldIsVisible:(CGSize)kbSize
{
    // Change the contentInset
    UIEdgeInsets contentInsets = self.scrollView.contentInset;

    // If iOS 7 or above then scrollview ends on bottom of screen. Thus, bottom of content inset should be moved by keyboard's height.
    if ([self iOS7OrAbove])
    {
        contentInsets.bottom = kbSize.height;
    }
    // Else scrollview ends above tabbar at bottom. Thus, bottom of content inset that should be moved is only by keyboard's height - tabbar's height
    else
    {
        contentInsets.bottom = kbSize.height - self.tabBarController.tabBar.bounds.size.height;
        DLog(@"Content inset bottom : %f", contentInsets.bottom);
    }

    [UIView animateWithDuration:0.25 animations:^{
        self.scrollView.contentInset = contentInsets;
        self.scrollView.scrollIndicatorInsets = contentInsets;

        CGFloat textFieldDistanceFromOrigin = self.selectedTextField.frame.origin.y - self.scrollView.contentOffset.y + self.selectedTextField.frame.size.height + TEXTFIELD_MARGIN;
        CGFloat visibleViewHeightAfterKeyboardDisplay = self.scrollView.bounds.size.height - kbSize.height + TOOLBAR_HEIGHT;

        // If active text field is hidden by keyboard, scroll it so it's visible
        if(textFieldDistanceFromOrigin > visibleViewHeightAfterKeyboardDisplay)
        {
            DLog(@"Textfield is hidden by keyboard.");

            // Calculate offset y value to add to scrollview's contentOffset so that textfield is visible then.
            CGFloat offsetToAdd = textFieldDistanceFromOrigin - visibleViewHeightAfterKeyboardDisplay;
            [self.scrollView setContentOffset:CGPointMake(0, self.scrollView.contentOffset.y + offsetToAdd) animated:YES];
        }

        if (textFieldDistanceFromOrigin < self.selectedTextField.bounds.size.height)
        {
            DLog(@"Textfield is above visible screen area.");

            [self.scrollView setContentOffset:CGPointMake(0, self.selectedTextField.frame.origin.y - TEXTFIELD_MARGIN) animated:YES];
        }
    }];
}

TextField delegate method :

// This is called when TextField becomes active
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    // Store TextField which has just become active
    self.selectedTextField = textField;
    [self moveViewSoThatTextFieldIsVisible:self.keyboardAndAccessoryViewSize];
}

Any ideas on what can be the issue here? I have already posted lot of codes, but I ask me if you need more of it.


Solution

  • I tried hard to find a method which was causing issue and no luck even if I set breakpoint in each method. Don't know if I miss one or something magical.

    But finally issue was in the code of checking if textfield is hidden behind or above navigation bar. I rectified it and now it works.

    In my case below is the correct way to check it. It can differ from yours.

    if (self.selectedTextField.frame.origin.y < self.scrollView.contentOffset.y)
    {
        // TextField is hidden behind navigation bar
    }