当键盘弹出时,iOS 11滚动无限

时间:2018-01-16 07:27:36

标签: ios objective-c uiscrollview constraints ios11

当我正在使用iOS本机应用上的login screen时,应用程序工作得非常好,直到ios 10.在iOS 11之后topLayoutGuidebottomLayoutGuide,我已将其替换为safeAreaLayoutGuide

但它仍然没有解决我的问题,当键盘弹出时,由于页脚视图,它会导致视图的无限滚动。 我的视图层次结构是

ScrollView

Username and Password fields with Login button

FooterEmptyView

Footer Label

FooterEmptyView中我有一个约束,只是为了从登录按钮腾出空间直到Footer Label,这将根据设备的大小增加或减少。 所有约束都以编程方式放置。

UIScrollView是否需要关注iOS 11? 在此先感谢!!

1 个答案:

答案 0 :(得分:1)

您提供的回购不包含您问题中描述的大部分功能。键盘事件就是其中之一。在运行代码的同时,我还得到了错误日志,这些约束存在冲突,因此至少在运行时已删除它们。此外,发布的应用程序根本不做任何事情,或者显示除了显示2个文本字段和按钮之外应该做什么。

由于您创建了一个新项目,您所做的就是修改了自动生成的视图控制器,因此没有理由不将此代码添加到您的问题中,而不是发布指向您的存储库的链接。

我仍然检查了你的代码并试图扣除你正在做的事情,并发现我对你的约束没有任何意义。也许至少一些评论会有所帮助。然后我重新创建了你的系统,在滚动视图上放置2个文本字段和一个带有约束的按钮。我还添加了可以移动字段的方法,以便它们从底部正确偏移,设计用于键盘框架更改时的事件。

我希望代码可以帮助您解决问题。完成后请删除此问题。

#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