Ios 带有粘性页脚UIView和动态高度内容的UIScrollView

Ios 带有粘性页脚UIView和动态高度内容的UIScrollView,ios,uiview,uiscrollview,autolayout,sticky-footer,Ios,Uiview,Uiscrollview,Autolayout,Sticky Footer,挑战时刻 假设我们有两个内容视图: 具有动态高度内容的UIView(可扩展UITextView)=红色 UIView作为页脚=蓝色 此内容位于UIScrollView=GEEN中 我应该如何使用自动布局来构造和处理约束,以归档以下所有情况 我想从下一个基本结构开始: - UIScrollView (with always bounce vertically) - UIView - Container - UIView - DynamicHeightContent

挑战时刻

假设我们有两个内容视图:

  • 具有动态高度内容的UIView(可扩展UITextView)=红色
  • UIView作为页脚=蓝色
  • 此内容位于UIScrollView=GEEN中

    我应该如何使用自动布局来构造和处理约束,以归档以下所有情况

    我想从下一个基本结构开始:

    - UIScrollView (with always bounce vertically)
        - UIView - Container
           - UIView - DynamicHeightContent
           - UIView - Sticky Footer
    
    键盘处理应通过代码监视通知
    UIKeyboardWillShowNotification
    UIKeyboardWillHideNotification
    完成。我们可以选择将键盘的结束帧高度设置为Container UIView bottom pin constraint或UIScrollView bottom contentInset

    现在,棘手的部分是粘性页脚

    • 如果有比整个容器视图更多的可用屏幕,我们如何确保粘性页脚视图保持在底部
    • 当显示/隐藏键盘时,我们如何知道可用的屏幕空间?我们肯定需要它
    • 这是我想要的结构吗
    多谢各位


    使用
    UITableView
    可能会比使用
    UITableView
    更好。最好不要使用自动布局。至少,我发现最好不要用它来做这些操作

    研究以下方面:

    • UITextView textViewDidChange
      • 使用
        sizeThatFits
        (限制宽度并使用FLT_MAX表示高度)更改文本视图的大小。更改框架,而不是内容大小
      • 调用
        UITableView
        beginUpdates/endUpdates更新表视图
      • 滚动到光标
    • ui键盘将显示通知
      notification
      • 在通过的
        NSNotification
        上,您可以调用userInfo(字典)和键
        UIKeyboardFrameBeginUserInfo
        。根据键盘大小的高度缩小表格视图的边框
      • 再次滚动到光标(因为布局将全部更改)
    • ui键盘将隐藏加密
      通知
      • 与显示通知相同,正好相反(增加表视图高度)
    要使页脚视图保持在底部,可以向表视图添加一个中间单元格,并使其根据文本大小和键盘是否可见而改变大小


    以上内容肯定需要您进行一些额外的操作-我不完全理解您的所有案例,但它肯定会让您开始。

    如果我理解整个任务,我的解决方案是将“红色”和“蓝色”视图放在一个容器视图中,并且在您知道动态内容大小(红色)的那一刻您可以计算容器的大小并设置scrollView内容大小。
    稍后,在键盘事件上,您可以调整内容视图和页脚视图之间的空白

    UITextView
    的文本内容相对较短时,内容视图的子视图(即文本视图和页脚)将无法通过约束指定其内容视图的大小。这是因为当文本内容较短时,内容视图的大小需要由滚动视图的大小决定

    更新:后一段不真实。可以在内容视图本身或内容视图的视图层次结构中的某个位置安装固定高度约束。可以在代码中设置固定高度约束的常量,以反映滚动视图的高度。后一段也反映了思维上的谬误。在纯自动布局方法中,内容视图的子视图不需要指定滚动视图的
    contentSize
    ;相反,最终必须由内容视图本身决定
    contentSize

    不管怎样,我决定采用苹果公司所谓的“混合方法”,在
    UIScrollView
    中使用自动布局(请参阅苹果公司的技术说明:)

    一些iOS技术作者,如Erica Sadun,更喜欢在几乎所有情况下使用混合方法(“iOS自动布局去神秘化”,第二版)

    在混合方法中,内容视图的帧和滚动视图的内容大小在代码中显式设置

    以下是我为此挑战创建的GitHub repo:。这是一个工作解决方案,包含布局更改的动画。它可以在不同大小的设备上工作。为了简单起见,我禁用了横向旋转

    对于那些不想下载并运行GitHub项目的人,我在下面介绍了一些要点(为了完整的实现,您必须查看GitHub项目):

    内容视图为橙色,文本视图为灰色,粘性页脚为蓝色。滚动时,文本在状态栏后面可见。我其实不喜欢这样,但它可以作为演示

    故事板中实例化的唯一视图是滚动视图,它是全屏的(即underlaps状态栏)

    出于测试的目的,我在蓝色的页脚上附加了一个双击手势识别器,以关闭键盘

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        self.scrollView.alwaysBounceVertical = YES;
    
        [self.scrollView addSubview:self.contentView];
        [self.contentView addSubview:self.textView];
        [self.contentView addSubview:self.stickyFooterView];
    
        [self configureConstraintsForContentViewSubviews];
    
        // Apple's mixed (a.k.a. hybrid) approach to laying out a scroll view with Auto Layout: explicitly set content view's frame and scroll view's contentSize (see Apple's Technical Note TN2154: https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
        CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text];
        CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:textViewHeight];
        // scroll view is fullscreen in storyboard; i.e., it's final on-screen geometries will be the same as the view controller's main view; unfortunately, the scroll view's final on-screen geometries are not available in viewDidLoad
        CGSize scrollViewSize = self.view.bounds.size;
    
        if (contentViewHeight < scrollViewSize.height) {
            self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, scrollViewSize.height);
        } else {
            self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight);
        }
    
        self.scrollView.contentSize = self.contentView.bounds.size;
    }
    
    - (void)configureConstraintsForContentViewSubviews
    {
        assert(_textView && _stickyFooterView); // for debugging
    
        // note: there is no constraint between the subviews along the vertical axis; the amount of vertical space between the subviews is determined by the content view's height
    
        NSString *format = @"H:|-(space)-[textView]-(space)-|";
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(SIDE_MARGIN)} views:@{@"textView": _textView}]];
    
        format = @"H:|-(space)-[footer]-(space)-|";
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(SIDE_MARGIN)} views:@{@"footer": _stickyFooterView}]];
    
        format = @"V:|-(space)-[textView]";
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(TOP_MARGIN)} views:@{@"textView": _textView}]];
    
        format = @"V:[footer(height)]-(space)-|";
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(BOTTOM_MARGIN), @"height": @(FOOTER_HEIGHT)} views:@{@"footer": _stickyFooterView}]];
    
        // a UITextView does not have an intrinsic content size; will need to install an explicit height constraint based on the size of the text; when the text is modified, this height constraint's constant will need to be updated
        CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text];
    
        self.textViewHeightConstraint = [NSLayoutConstraint constraintWithItem:self.textView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:textViewHeight];
    
        [self.textView addConstraint:self.textViewHeightConstraint];
    }
    
    - (void)keyboardUp:(NSNotification *)notification
    {
        // when the keyboard appears, extraneous vertical space between the subviews is eliminated–if necessary; i.e., vertical space between the subviews is reduced to the minimum if this space is not already at the minimum
    
        NSDictionary *info = [notification userInfo];
        CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
        keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
        double duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    
        CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:self.textView.bounds.size.height];
        CGSize scrollViewSize = self.scrollView.bounds.size;
    
        [UIView animateWithDuration:duration animations:^{
    
            self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight);
            self.scrollView.contentSize = self.contentView.bounds.size;
            UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, keyboardRect.size.height, 0);
            self.scrollView.contentInset = insets;
            self.scrollView.scrollIndicatorInsets = insets;
    
            [self.view layoutIfNeeded];
    
        } completion:^(BOOL finished) {
    
            [self scrollToCaret];
        }];
    }
    
    -(void)viewDidLoad
    {
    [超级视图下载];
    self.scrollView.alwaysBounceVertical=是;
    [self.scrollView addSubview:self.contentView];
    [self.contentView addSubview:self.textView];
    [self.contentView addSubview:self.stickyFooterView];
    [内容视图子视图的自配置约束];
    //苹果的混合(又称混合)方法通过自动布局来布局滚动视图:明确设置内容视图的框架和滚动视图的内容大小(参见苹果的技术说明TN2154:https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
    CGFloat textViewHeight=[self-calculateHeightForTextViewWithString:self.textView.text];
    CGFloat contentViewHeight=[自计算重量]