'iOS 11 scrolling infinite when keyboard pops up

As im working on with the login screen on iOS native app, app worked exactly fine till ios 10. on iOS 11 onwards the topLayoutGuide and bottomLayoutGuide, i have replace them with the safeAreaLayoutGuide.

But it still doesnt fix my issue, where on when keyboard pops up, it leads to infinite scroll of the view, because of the footerview. My view hierarchy is that

ScrollView

Username and Password fields with Login button

FooterEmptyView

Footer Label

Where in FooterEmptyView i have a constraint as just to make space from login button till the Footer Label, which will increase or decrease depending on size of device. All the constraints are programmatically placed.

Is it something UIScrollView issue which i need to take care for iOS 11? Thanks in Advance!!



Solution 1:[1]

The repo you provided did not include most of features described in your question. Keyboard events being one of them. While running the code I also got error logs that constraints were in conflict so at least on of them has been removed in runtime. Further more the app posted does nothing at all or shows what it should be doing beyond displaying 2 text fields and a button.

Since you have created a new project into which all you did was modified its autogenerated view controller there was no reason not to add this code into your question instead of posting a link to your repository.

Still I have I checked your code and tried to deduct what you were doing and found out I can not make any sense in your constrains. Maybe at least some comments would help. I have then recreated your system, putting 2 text fields and a button on the scroll view with constraints. I have also added methods that may move fields so they are correctly offset from bottom which is designed to be used on events when keyboard frame has changed.

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *scrollViewContentView;

@property (nonatomic, strong) UITextField *usernameTextField;
@property (nonatomic, strong) UITextField *passwordTextField;
@property (nonatomic, strong) UIButton *loginButton;

@property (nonatomic, strong) NSLayoutConstraint *usernameBottomConstraint; // Restrict for keyboard
@property (nonatomic, strong) NSLayoutConstraint *passwordBottomConstraint; // Restrict for keyboard

@end

@implementation ViewController

- (UIScrollView *)scrollView {
    // Lazy load
    if(_scrollView == nil) {
        _scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
        [self.view addSubview:_scrollView];
        _scrollView.translatesAutoresizingMaskIntoConstraints = NO;
        [self attachBorderConstraint:_scrollView to:self.view];
    }
    return _scrollView;
}

- (UIView *)scrollViewContentView {
    // Lazy load
    if(_scrollViewContentView == nil) {
        _scrollViewContentView = [[UIView alloc] initWithFrame:self.view.bounds];
        [self.scrollView addSubview:_scrollViewContentView];
        _scrollViewContentView.translatesAutoresizingMaskIntoConstraints = NO;
        NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:_scrollViewContentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.scrollView attribute:NSLayoutAttributeTop multiplier:1.0 constant:10.0];
        topConstraint.priority = (UILayoutPriority)800; // Needs a bit lower constraints when keyboard shows and the whole thing goes a bit off the screen
        [self.scrollView addConstraint:topConstraint];
        [self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:_scrollViewContentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.scrollView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10.0]];
        [self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:_scrollViewContentView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.scrollView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:10.0]];
        [self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:_scrollViewContentView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.scrollView attribute:NSLayoutAttributeRight multiplier:1.0 constant:10.0]];
    }
    return _scrollViewContentView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // TODO: remove testing offsets gesture and its method
    [self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTestGesture)]];
    
    // Set some colors for debugging and to see what happens. Later remove these
    self.scrollView.backgroundColor = [UIColor redColor];
    self.scrollViewContentView.backgroundColor = [UIColor greenColor];
    
    CGFloat margins = 20.0f;
    CGFloat fieldHeight = 44.0f;
    CGFloat separatorHeight = 12.0f;
    
    // Username text field
    {
        UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(0, margins, self.view.frame.size.width-margins*2.0, fieldHeight)];
        field.placeholder = @"Enter User Name";
        field.backgroundColor = [UIColor whiteColor];
        field.translatesAutoresizingMaskIntoConstraints = NO;
        [self.scrollViewContentView addSubview:field];
        
        // We want it on top of content view and have fixed offset from borders
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:margins]];
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:margins]];
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeRight multiplier:1.0 constant:-margins]];
        [field addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:fieldHeight]];
        
        self.usernameTextField = field;
    }
    
    // Password text field
    {
        UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(0, margins, self.view.frame.size.width-margins*2.0, fieldHeight)];
        field.placeholder = @"Enter Password";
        field.backgroundColor = [UIColor whiteColor];
        field.translatesAutoresizingMaskIntoConstraints = NO;
        [self.scrollViewContentView addSubview:field];
        
        // We want it below username field and have fixed offset from borders
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.usernameTextField attribute:NSLayoutAttributeBottom multiplier:1.0 constant:separatorHeight]];
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:margins]];
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeRight multiplier:1.0 constant:-margins]];
        [field addConstraint:[NSLayoutConstraint constraintWithItem:field attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:fieldHeight]];
        
        self.passwordTextField = field;
    }
    
    // Login button
    {
        UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, margins, self.view.frame.size.width-margins*2.0, 50.0)];
        [button setTitle:@"Login" forState:UIControlStateNormal];
        [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
        button.titleLabel.font = [UIFont systemFontOfSize:16.0];  //[UIFont fontWithName:@"MarkerFelt-Thin" size:16];
        button.translatesAutoresizingMaskIntoConstraints = NO;
        button.backgroundColor = [UIColor whiteColor];
        
        [self.scrollViewContentView addSubview:button];
        
        // We want it below password field and have fixed offset from borders
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.passwordTextField attribute:NSLayoutAttributeBottom multiplier:1.0 constant:separatorHeight]];
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:margins]];
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeRight multiplier:1.0 constant:-margins]];
        [button addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:50.0]];
        [self.scrollViewContentView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.scrollViewContentView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-margins]];
        
        self.loginButton = button;
    }
    
    // Now to position the content view insde scroll view
    
    // Horizontally we want to constrain it to borders and restrict maximum width:
    {
        // Must be centered
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.scrollViewContentView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
        [self.scrollView addConstraint:constraint];
    }
    {
        // Constraint width
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.scrollViewContentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:400.0];
        constraint.priority = (UILayoutPriority)500; // Low priority so it will shrink when screen width will be too low
        [self.scrollViewContentView addConstraint:constraint];
    }
    
    // Vertically we want to constrain it to borders and restrict maximum width:
    {
        // Must be centered
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.scrollViewContentView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0];
        constraint.priority = (UILayoutPriority)500;
        [self.scrollView addConstraint:constraint];
    }
    {
        // Constraint height
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.scrollViewContentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:400.0];
        constraint.priority = (UILayoutPriority)200; // Low priorty so it will autosize
        [self.scrollViewContentView addConstraint:constraint];
    }
    
    self.usernameBottomConstraint = [NSLayoutConstraint constraintWithItem:self.usernameTextField attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-0];
    self.passwordBottomConstraint = [NSLayoutConstraint constraintWithItem:self.passwordTextField attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-0];
    [self.view addConstraints:@[self.usernameBottomConstraint, self.passwordBottomConstraint]];
    
}

- (void)onTestGesture {
    static int testState = 0;
    testState++;
    
    switch (testState%3) {
        case 0:
            // Keyboard hidden
            [self restrictUsernameBottomOffsetTo:0.0];
            [self restrictPasswrodBottomOffsetTo:0.0];
            break;
        case 1:
            // Keyboard shown for username
            [self restrictUsernameBottomOffsetTo:350.0];
            break;
        case 2:
            // Keyboard shown for password
            [self restrictPasswrodBottomOffsetTo:350.0];
            break;
        default:
            break;
    }
    
    [UIView animateWithDuration:0.3 animations:^{
        [self.view layoutIfNeeded];
    }];
}

- (void)restrictUsernameBottomOffsetTo:(CGFloat)newOffset {
    self.passwordBottomConstraint.constant = -0.0f;
    self.usernameBottomConstraint.constant = -newOffset;
}
- (void)restrictPasswrodBottomOffsetTo:(CGFloat)newOffset {
    self.usernameBottomConstraint.constant = -0.0f;
    self.passwordBottomConstraint.constant = -newOffset;
}

- (NSArray<NSLayoutConstraint *> *)attachBorderConstraint:(UIView *)subview to:(UIView *)superview {
    NSMutableArray *constraints = [[NSMutableArray alloc] init];
    [constraints addObject:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
    [constraints addObject:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
    [constraints addObject:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
    [constraints addObject:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
    [superview addConstraints:constraints];
    return constraints;
}


@end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 General Grievance