Gas*_*ton 10 uiscrollview uiview sticky-footer ios autolayout
挑战时间!
想象一下,我们有2个内容视图:
此内容位于UIScrollView = GEEN中
我应该如何通过自动布局来构建和处理约束以存档以下所有情况?
我正在考虑下一个基本结构:
- UIScrollView (with always bounce vertically)
- UIView - Container
- UIView - DynamicHeightContent
- UIView - Sticky Footer
Run Code Online (Sandbox Code Playgroud)
键盘处理应该通过代码观看通知UIKeyboardWillShowNotification
和UIKeyboardWillHideNotification
.我们可以选择将键盘的结束帧高度设置为Container UIView底部引脚约束或UIScrollView底部的contentInset.
现在,棘手的部分是粘性页脚.
谢谢.
当文本内容UITextView
相对较短时,内容视图的子视图(即文本视图和页脚)将无法通过约束来规定其内容视图的大小.那是因为当文本内容很短时,内容视图的大小需要由滚动视图的大小决定.
更新:后一段是不真实的.您可以在内容视图本身或内容视图的视图层次结构中的某个位置安装固定高度约束.可以在代码中设置固定高度约束的常量以反映滚动视图的高度.后一段也反映了思维的谬误.在纯自动布局方法中,内容视图的子视图不需要指定滚动视图contentSize
; 相反,内容视图本身最终必须决定contentSize
.
无论如何,我决定采用Apple所谓的"混合方法"来使用Auto Layout UIScrollView
(参见Apple的技术说明:https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
一些iOS技术作家,如Erica Sadun,更喜欢在几乎所有情况下使用混合方法("iOS Auto Layout Demystified",第2版).
在混合方法中,内容视图的框架和滚动视图的内容大小在代码中显式设置.
这是我为这次挑战创建的GitHub回购:https://github.com/bilobatum/StickyFooterAutoLayoutChallenge.这是一个完整的布局变化动画的工作解决方案.它适用于不同大小的设备.为简单起见,我禁用了旋转到横向.
对于那些不想下载和运行GitHub项目的人,我在下面列出了一些要点(对于完整的实现,你必须看看GitHub项目):
内容视图为橙色,文本视图为灰色,粘性页脚为蓝色.滚动时,状态栏后面会显示文本.我实际上并不喜欢这样,但它对于演示来说很好.
在故事板中实例化的唯一视图是滚动视图,它是全屏(即,状态栏下方).
出于测试目的,我将双击手势识别器附加到蓝色页脚以解除键盘.
- (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];
}];
}
Run Code Online (Sandbox Code Playgroud)
虽然这个演示应用程序的自动布局组件花了一些时间,但我花了相当多的时间来滚动与UITextView
嵌套在一个内部相关的问题UIScrollView
.