UIKit: UIScrollView автоматически прокручивается, когда subview увеличивает свою ширину за краем экрана
В контексте iPhone:
У меня есть UIScrollView
содержащий UIImage
. Когда пользователь UIImage
на экран внутри UIImage
, UITextField
добавляется там, где пользователь коснулся. Пользователь может редактировать этот UITextField
, и текстовое поле автоматически изменит свой размер в зависимости от того, был ли текст добавлен или удален.
Когда редактируемое UITextField
увеличивает его ширину, представление прокрутки автоматически прокручивается, чтобы показать увеличенную ширину.
Проблема возникает потому, что автоматическая прокрутка текстового поля не учитывает значение y на экране.
Например, скажем, пользователь добавил текстовое поле внизу изображения. Когда они перейдут к редактированию этого текстового поля, появится клавиатура, скрывающая текстовое поле. У меня есть код для прокрутки экрана, чтобы показать текстовое поле. Проблема возникает, когда пользователь вводит столько текста, что это текстовое поле выходит за край экрана. Когда это происходит, экран прокручивается по горизонтали, чтобы вместить более широкий текст, но также и по вертикали - вертикальная прокрутка заканчивается тем, что скрывает текстовое поле, в основном сводя на нет все, что я сделал, чтобы показать текстовое поле.
Код для отображения текстового поля, если оно скрыто клавиатурой:
- (void)keyboardWasShown:(NSNotification*)notification
{
NSDictionary* info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
self.offset = self.contentOffset;
CGRect frame = self.frame;
// self.activeField is the name of the field that is the current first responder - this just adds a little bit of padding
frame.size.height -= keyboardSize.height + (self.activeField.frame.size.height * 2);
if (!CGRectContainsPoint(frame, self.activeField.frame.origin)) {
CGPoint scrollPoint = CGPointMake(self.offset.x, self.activeField.frame.origin.y - keyboardSize.height + (activeField.frame.size.height * 2));
[self setContentOffset:scrollPoint animated:YES];
}
}
Вот код для увеличения размера текстового поля:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
CGSize stringSize = [string sizeWithFont:textField.font];
CGSize newSize = [newString sizeWithFont:textField.font];
// Make textField wider if we're close to running up against it
if (newSize.width > (textField.frame.size.width - self.widthOffset)) {
CGRect textFieldFrame = textField.frame;
if (stringSize.width > self.widthOffset) {
textFieldFrame.size.width += stringSize.width;
}
textFieldFrame.size.width += self.widthOffset;
textField.frame = textFieldFrame;
}
// Shrink the textField if there is too much wasted space
if ((textField.frame.size.width - newSize.width) > self.widthOffset) {
CGRect textFieldFrame = textField.frame;
textFieldFrame.size.width = newSize.width + self.widthOffset;
textField.frame = textFieldFrame;
}
return YES;
}
Вопрос в следующем: как заставить UIScrollView
уважать значение y самого себя при автоматической прокрутке?
Ответы
Ответ 1
По сути, setFrame
для UIScrollView
перенастроит offset
прокрутки, сделанное _adjustContentOffsetIfNecessary
. Поскольку этот метод является частным и не документирован, мы мало что можем догадаться о том, как произойдет корректировка. Существует два способа остановить установку ненужной прокрутки или неправильного offset
:
1) сбросить offset
UIScrollView
после применения setFrame
. Это вы можете сделать, если вы намеренно UIScrollView
фрейм UIScrollView
на основе некоторых вычислений.
CGPoint oldOffset = [scrollView contentOffset];
scrollView.frame = CGRectMake(newFrame);
[scrollView setContentOffset:oldOffset];
2) применить изменения offset
без анимации. В keyboardWasShown
измените [self setContentOffset:scrollPoint animated:YES]; to
[self setContentOffset:scrollPoint animated:NO];
[self setContentOffset:scrollPoint animated:YES]; to
[self setContentOffset:scrollPoint animated:NO];
Причина: когда применяется более одного offset
с включенной анимацией, offset
результата неоднозначно. Здесь внутренний метод (_adjustContentOffsetIfNecessary
) применяет изменение смещения, а другой выполняется вашим кодом. Вы можете заметить это, если попытаетесь зарегистрировать все смещения, применяемые в методе делегата UIScrollView
:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(@" offset: %@", NSStringFromCGPoint(scrollView.conentOffset))
}
Позвольте мне знать, если это помогает.
Ответ 2
Возможным обходным решением может быть ответ на проверку метода делегата scrollViewDidScroll
, чтобы увидеть, снова ли скрыт UITextField
, а затем повторно прокрутить, если необходимо. Кажется, что это немного взломан, но похоже, что поведение автоматической прокрутки UIScrollView
- это то, что вам мешает, и если нет никакого способа напрямую повлиять на него, единственный вариант - это обойти его. Однако есть и недостаток, что если вы это сделаете, то, похоже, прокручивается дважды.
Если поведение автоматической прокрутки происходит только тогда, когда UITextField
расширяется за пределы края экрана, вы также можете переместить поле, чтобы оно оставалось полностью видимым, если оно похоже на расширение за пределы экрана.
Ответ 3
Вместо изменения смещения содержимого измените рамку отображения прокрутки.
- (void)keyboardWill:(NSNotification*)notification
{
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// Update the scroll view frame to the region not covered by the keyboard.
self.frame = CGRectMake(self.fullSizedFrame.origin.x,
self.fullSizedFrame.origin.y,
self.fullSizedFrame.size.width,
self.fullSizedFrame.size.height - keyboardSize.height);
}
- (void)keyboardWillHide:(NSNotification*)notification
{
// Set the frame back to the original frame before the keyboard was shown.
self.frame = self.fullSizedFrame;
}
Если вы не позволяете пользователю изменять ориентацию экрана, то при первом отображении fullSizedFrame может быть установлен на исходный кадр представления. Если изменения в ориентации разрешены, вам необходимо вычислить соответствующее значение для fullSizedFrame на основе ориентации.