Ответ 1
Когда текстовое содержимое UITextView
относительно короткое, подвид представлений содержимого (то есть текстовый вид и нижний колонтитул) не сможет определять размер их содержимого через ограничения. Это связано с тем, что, когда текстовый контент короток, размер представления содержимого должен определяться размером прокрутки.
Обновление: Последний абзац не соответствует действительности. Вы можете установить ограничение с фиксированной высотой либо в самом представлении контента, либо где-нибудь в иерархии представлений представления содержимого. Константа ограничения фиксированной высоты может быть установлена в коде, чтобы отображать высоту представления прокрутки. Последний абзац также отражает ошибочность мышления. В подходе с автоматическим макетом в представлении просмотра содержимого нет необходимости прокручивать вид прокрутки contentSize
; вместо этого это представление контента, которое в конечном счете должно диктовать contentSize
.
Несмотря на это, я решил пойти с Apple так называемым "смешанным подходом" для использования Auto Layout с UIScrollView
(см. Техническое примечание Apple: https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
Некоторые технические писатели iOS, такие как Эрика Садун, предпочитают использовать смешанный подход практически во всех ситуациях ( "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 mixed (a.k.a. hybrid) approach to laying out a scroll view with Auto Layout: explicitly set content view frame and scroll view contentSize (see Apple 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 final on-screen geometries will be the same as the view controller main view; unfortunately, the scroll view 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 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 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];
}];
}
Хотя компонент Auto Layout этого демонстрационного приложения занял некоторое время, я потратил почти столько же времени на прокрутку проблем, связанных с UITextView
, вложенными внутри UIScrollView
.