问题描述
挑战时间!
假设我们有 2 个内容视图:
Imagine we have 2 content views:
- 具有动态高度内容的 UIView(可扩展 UITextView)= RED
- UIView 作为页脚 = BLUE
此内容在 UIScrollView = GEEN 内
This content is inside a UIScrollView = GEEN
我应该如何构建和处理具有自动布局的约束以存档以下所有情况?
How should I structure and handle the constraints with auto-layout to archive all the following cases?
我正在考虑从下一个基本结构开始:
I am thinking next basic structure to start with:
- UIScrollView (with always bounce vertically)
- UIView - Container
- UIView - DynamicHeightContent
- UIView - Sticky Footer
键盘处理应该通过代码观察通知UIKeyboardWillShowNotification
和UIKeyboardWillHideNotification
来完成.我们可以选择将键盘的结束帧高度设置为 Container UIView 底部引脚约束或 UIScrollView 底部 contentInset.
Keyboard handling should be done by code watching notifications UIKeyboardWillShowNotification
and UIKeyboardWillHideNotification
. We can chose to set the keyboard's end frame height to Container UIView bottom pin constraint or to the UIScrollView bottom contentInset.
现在,棘手的部分是粘性页脚.
Now, the tricky part is the sticky footer.
- 如果可用屏幕多于整个容器视图,我们如何确保粘性页脚 UIView 保持在底部?
- 当键盘显示/隐藏时,我们如何知道可用的屏幕空间?我们肯定会需要它.
- 我想要的这个结构是对的吗?
谢谢.
推荐答案
当UITextView
的文本内容比较短时,内容视图的子视图(即文本视图和页脚)不会能够通过约束来决定其内容视图的大小.这是因为当文本内容很短时,内容视图的大小需要由滚动视图的大小决定.
When the text content of the UITextView
is relatively short, the content view's subviews (i.e., the text view and footer) will not be able to dictate the size of their content view through constraints. That's because when the text content is short, the content view's size will need to be determined by the scroll view's size.
更新:后一段是不真实的.您可以在内容视图本身或内容视图的视图层次结构中的某处安装固定高度约束.可以在代码中设置固定高度约束的常量以反映滚动视图的高度.后一段也反映了一个思维上的谬误.在纯自动布局方法中,内容视图的子视图不需要指定滚动视图的 contentSize
;相反,最终必须决定 contentSize
的是内容视图本身.
Update: The latter paragraph is untrue. You could install a fixed-height constraint either on the content view itself or somewhere in the content view's view hierarchy. The fixed-height constraint's constant could be set in code to reflect the height of the scroll view. The latter paragraph also reflects a fallacy in thinking. In a pure Auto Layout approach, the content view's subviews don't need to dictate the scroll view's contentSize
; instead, it's the content view itself that ultimately must dictate the contentSize
.
无论如何,我决定采用 Apple 所谓的混合方法",通过 UIScrollView
使用自动布局(请参阅 Apple 的技术说明:https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
Regardless, I decided to go with Apple's so-called "mixed approach" for using Auto Layout with UIScrollView
(see Apple's Technical Note: https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
一些 iOS 技术作者,如 Erica Sadun,更喜欢在几乎所有情况下使用混合方法(iOS 自动布局揭秘",第 2 版).
Some iOS technical writers, like Erica Sadun, prefer using the mixed approach in pretty much all situations ("iOS Auto Layout Demystified", 2nd Ed.).
在混合方法中,内容视图的框架和滚动视图的内容大小在代码中明确设置.
In the mixed approach, the content view's frame and the scroll view's content size are explicitly set in code.
这是我为此挑战创建的 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];
}];
}
虽然这个演示应用程序的自动布局组件花费了一些时间,但我在与嵌套在 UIScrollView
内的 UITextView
相关的滚动问题上花费了几乎同样多的时间.
这篇关于带有粘性页脚 UIView 和动态高度内容的 UIScrollView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!