UIScrollView с липким нижним колонтитулом UIView и динамическим содержимым высоты
Вызов времени!
Представьте, что у нас есть 2 вида контента:
- UIView с динамически изменяемым содержимым высоты (расширяемый UITextView) = RED
- UIView как нижний колонтитул = синий
Это содержимое находится внутри UIScrollView = GEEN
Как я должен структурировать и обрабатывать ограничения с автоматической компоновкой для архивирования всех следующих случаев?
Я думаю, что следующая базовая структура для начала:
- UIScrollView (with always bounce vertically)
- UIView - Container
- UIView - DynamicHeightContent
- UIView - Sticky Footer
Обработка клавиатуры должна осуществляться путем просмотра кода уведомления UIKeyboardWillShowNotification
и UIKeyboardWillHideNotification
. Мы можем выбрать, чтобы установить высоту конечного кадра клавиатуры для контейнера UIView bottom pin constraint или для UIScrollView bottom contentInset.
- Как убедиться, что липкий нижний колонтитул UIView остается внизу, если доступно больше экрана, чем весь вид контейнера?
- Как узнать доступное пространство экрана, когда клавиатура отображается / скрыта? он нам обязательно понадобится.
- Is is it правильно ли я предназначаю эту структуру?
Спасибо.
3 ответа:
Когда текстовое содержимое
UITextView
является относительно коротким, подвиды представления содержимого (т. е. текстовое представление и нижний колонтитул) не смогут диктовать размер своего представления содержимого через ограничения. Это связано с тем, что когда текстовое содержимое короткое, размер представления содержимого должен быть определен размером представления прокрутки.Обновление: последний абзац не соответствует действительности. Вы можете установить ограничение фиксированной высоты либо на самом представлении содержимого, либо где-то в содержимом иерархия представлений вида. Константа ограничения фиксированной высоты может быть задана в коде для отражения высоты вида прокрутки. Последний пункт также отражает ошибочность мышления. При чисто автоматическом подходе к компоновке подвиды представления содержимого не должны диктовать представление прокрутки
contentSize
; вместо этого само представление содержимого в конечном счете должно диктоватьcontentSize
.Несмотря на это, я решил пойти с так называемым "смешанным подходом" Apple для использования автоматической компоновки с
UIScrollView
(см. Техническое Примечание: 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'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]; }]; }
UITextView
вUIScrollView
.
Вместо того, чтобы использовать
UIScrollView
, вам, скорее всего, будет лучше использоватьUITableView
. Также может быть лучше не использовать автоматическую раскладку. По крайней мере, я считаю, что лучше не использовать его для подобных манипуляций.Обратите внимание на следующее:
UITextView textViewDidChange
- измените размер текстового представления с помощью
sizeThatFits
(ограничение ширины и использование FLT_MAX для высоты). Измените рамку, а не размер содержимого.- Вызов
UITableView
beginUpdates / endUpdates для обновления таблицы вид- прокрутите до курсора
UIKeyboardWillShowNotification
уведомление
- на
NSNotification
, который приходит через, вы можете вызвать userInfo (словарь), и ключUIKeyboardFrameBeginUserInfoKey
. Уменьшите рамку табличного представления в зависимости от высоты размера клавиатуры.- прокрутите до курсора еще раз (так как все макеты будут изменены)
UIKeyboardWillHideNotification
уведомление
- то же самое, что и уведомление show, только наоборот (увеличение табличного представления высота)
Чтобы нижний колонтитул оставался внизу, можно добавить промежуточную ячейку в табличное представление и изменить ее размер в зависимости от размера текста и того, видна ли клавиатура.
Вышеизложенное, безусловно, потребует некоторых дополнительных манипуляций с вашей стороны - я не полностью понимаю все ваши случаи, но это определенно должно помочь вам начать.
Если я понимаю всю задачу, мое решение заключается в том, чтобы поместить" красный "и" синий " виды в один вид контейнера, и в тот момент, когда вы знаете размер динамического содержимого (красный), вы можете вычислить размер контейнера и установить размер содержимого scrollView. Позже, в случае событий клавиатуры, вы можете настроить пустое пространство между контентом и нижним колонтитулом