Создание прокрутки UITableView при выделении текстового поля - PullRequest
245 голосов
/ 27 февраля 2009

После долгих проб и ошибок я сдаюсь и задаю вопрос. Я видел много людей с похожими проблемами, но не мог правильно ответить на все ответы.

У меня есть UITableView, который состоит из пользовательских ячеек. Ячейки состоят из 5 текстовых полей, расположенных рядом друг с другом (вроде сетки).

Когда я пытаюсь прокрутить и отредактировать ячейки внизу UITableView, мне не удается правильно расположить свои ячейки над клавиатурой.

Я видел много ответов, говорящих об изменении размеров вида и т. Д., Но ни один из них до сих пор не работал должным образом.

Может ли кто-нибудь уточнить «правильный» способ сделать это на конкретном примере кода?

Ответы [ 45 ]

6 голосов
/ 03 августа 2011

Объединение и заполнение пробелов из нескольких ответов (в частности, Ortwin Gentz, пользователь 98013) и другого поста, это будет работать из коробки для SDK 4.3 на iPad в портретном или альбомном режиме:

@implementation UIView (FindFirstResponder)
- (UIResponder *)findFirstResponder
{
  if (self.isFirstResponder) {        
    return self;     
  }

  for (UIView *subView in self.subviews) {
    UIResponder *firstResponder = [subView findFirstResponder];
    if (firstResponder != nil) {
      return firstResponder;
    }
  }

  return nil;
}
@end

@implementation MyViewController

- (UIResponder *)currentFirstResponder {
  return [self.view findFirstResponder];
}

- (IBAction)editingEnded:sender {
  [sender resignFirstResponder];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
  [textField resignFirstResponder];
  return NO;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField {
  UITableViewCell *cell = (UITableViewCell*) [[textField superview] superview];
  [_tableView scrollToRowAtIndexPath:[_tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)keyboardWillShow:(NSNotification*)notification {
  if ([self currentFirstResponder] != nil) {
    NSDictionary* userInfo = [notification userInfo];

    // we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0
    NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"];
    if (!keyboardFrameValue) {
      keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"];
    }

    // Reduce the tableView height by the part of the keyboard that actually covers the tableView
    CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
    CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
    CGRect frame = _tableView.frame;
    if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
      windowRect = CGRectMake(windowRect.origin.y, windowRect.origin.x, windowRect.size.height, windowRect.size.width);
      viewRectAbsolute = CGRectMake(viewRectAbsolute.origin.y, viewRectAbsolute.origin.x, viewRectAbsolute.size.height, viewRectAbsolute.size.width);
    }
    frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
    _tableView.frame = frame;
    [UIView commitAnimations];

    UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview;
    NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell];

    // iOS 3 sends hide and show notifications right after each other
    // when switching between textFields, so cancel -scrollToOldPosition requests
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    _topmostRowBeforeKeyboardWasShown = [[_tableView indexPathsForVisibleRows] objectAtIndex:0];
    [_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
  }
}

- (void) scrollToOldPosition {
  [_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)keyboardWillHide:(NSNotification*)notification {
  if ([self currentFirstResponder] != nil) {

    NSDictionary* userInfo = [notification userInfo];

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
    _tableView.frame = self.view.bounds;
    [UIView commitAnimations];

    [self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1];
  }
}   

@end
5 голосов
/ 20 марта 2015

Использование UITextField's delegate метод:

Swift

func textFieldShouldBeginEditing(textField: UITextField) -> bool {
  let txtFieldPosition = textField.convertPoint(textField.bounds.origin, toView: yourTableViewHere)
  let indexPath = yourTablViewHere.indexPathForRowAtPoint(txtFieldPosition)
  if indexPath != nil {
     yourTablViewHere.scrollToRowAtIndexPath(indexPath!, atScrollPosition: .Top, animated: true)
  }
  return true
}

Objective-C

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
  CGPoint txtFieldPosition = [textField convertPoint:CGPointZero toView: yourTablViewHere];
  NSLog(@"Begin txtFieldPosition : %@",NSStringFromCGPoint(txtFieldPosition));
  NSIndexPath *indexPath = [yourTablViewHere indexPathForRowAtPoint:txtFieldPosition];

  if (indexPath != nil) {
     [yourTablViewHere scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
  }
  return YES;
}
5 голосов
/ 01 мая 2009

Если вы используете uitableview для размещения своих текстовых полей ( от Джеффа Ламарша ), вы можете просто прокрутить табличное представление, используя метод делегата, подобный этому.

(Примечание: мои текстовые поля хранятся в массиве с тем же индексом, что и строка в табличном представлении)

- (void) textFieldDidBeginEditing:(UITextField *)textField
    {

        int index;
        for(UITextField *aField in textFields){

            if (textField == aField){
                index = [textFields indexOfObject:aField]-1;
            }
        }

         if(index >= 0) 
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];

        [super textFieldDidBeginEditing:textField];
    }
5 голосов
/ 12 июля 2009

Уведомления клавиатуры работают, но пример кода Apple для этого предполагает, что представление прокрутки является корневым представлением окна. Обычно это не тот случай. Вы должны компенсировать вкладки и т. Д., Чтобы получить правильное смещение.

Это проще, чем кажется. Вот код, который я использую в UITableViewController. У него есть две переменные экземпляра: hiddenRect и keyboardShown.

// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification {
    if (keyboardShown)
        return;

    NSDictionary* info = [aNotification userInfo];

    // Get the frame of the keyboard.
    NSValue *centerValue = [info objectForKey:UIKeyboardCenterEndUserInfoKey];
    NSValue *boundsValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
    CGPoint keyboardCenter = [centerValue CGPointValue];
    CGRect keyboardBounds = [boundsValue CGRectValue];
    CGPoint keyboardOrigin = CGPointMake(keyboardCenter.x - keyboardBounds.size.width / 2.0,
                                         keyboardCenter.y - keyboardBounds.size.height / 2.0);
    CGRect keyboardScreenFrame = { keyboardOrigin, keyboardBounds.size };


    // Resize the scroll view.
    UIScrollView *scrollView = (UIScrollView *) self.tableView;
    CGRect viewFrame = scrollView.frame;
    CGRect keyboardFrame = [scrollView.superview convertRect:keyboardScreenFrame fromView:nil];
    hiddenRect = CGRectIntersection(viewFrame, keyboardFrame);

    CGRect remainder, slice;
    CGRectDivide(viewFrame, &slice, &remainder, CGRectGetHeight(hiddenRect), CGRectMaxYEdge);
    scrollView.frame = remainder;

    // Scroll the active text field into view.
    CGRect textFieldRect = [/* selected cell */ frame];
    [scrollView scrollRectToVisible:textFieldRect animated:YES];

    keyboardShown = YES;
}


// Called when the UIKeyboardDidHideNotification is sent
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
    // Reset the height of the scroll view to its original value
    UIScrollView *scrollView = (UIScrollView *) self.tableView;
    CGRect viewFrame = [scrollView frame];
    scrollView.frame = CGRectUnion(viewFrame, hiddenRect);

    keyboardShown = NO;
}
5 голосов
/ 29 сентября 2012

Мой подход:

Сначала я создаю подкласс UITextField и добавляю свойство indexPath. В методе cellFor ... я передаю свойство indexPath.

Затем я добавляю следующий код:

UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:textField.indexPath];

CGPoint cellPoint = [cell convertPoint:textField.center toView:self.tableView];
[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, cellPoint.y-50);}];

to textFieldShould / WillBegin ... и т. Д.

Когда клавиатура исчезает, вы должны перевернуть ее с помощью:

[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, 0);}];
5 голосов
/ 21 сентября 2010

Если вы используете Three20, то используйте свойство autoresizesForKeyboard. Просто установите в вашем контроллере метод -initWithNibName:bundle

self.autoresizesForKeyboard = YES

Это заботится о:

  1. Прослушивание уведомлений клавиатуры и настройка рамки табличного представления
  2. Прокрутка до первого респондента

Готово и сделано.

5 голосов
/ 09 декабря 2010

Правильный ответ - ответ Сэма Хо:

"Если вы используете UITableViewController вместо UIViewController, он автоматически сделает это.".

Просто убедитесь, что ваш UITableView подключен к свойству TableView UITableViewController (например, не добавляйте его как подпредставление свойства View UITableViewController).

Также убедитесь, что для свойства AutoresizingMask вашего UITableView установлено значение FlexibleHeight

4 голосов
/ 04 августа 2009

Более продуманное решение. Он проскальзывает в методы делегата UITextField, поэтому он не требует беспорядок с уведомлениями UIKeyboard.

Замечания по реализации:

kSettingsRowHeight - высота UITableViewCell.

offsetTarget и offsetThreshold основаны на kSettingsRowHeight. Если вы используете другую высоту строки, установите эти значения для свойства y точки. [alt: рассчитать смещение строки другим способом.]

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
CGFloat offsetTarget    = 113.0f; // 3rd row
CGFloat offsetThreshold = 248.0f; // 6th row (i.e. 2nd-to-last row)

CGPoint point = [self.tableView convertPoint:CGPointZero fromView:textField];

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

CGRect frame = self.tableView.frame;
if (point.y > offsetThreshold) {
    self.tableView.frame = CGRectMake(0.0f,
                      offsetTarget - point.y + kSettingsRowHeight,
                      frame.size.width,
                      frame.size.height);
} else if (point.y > offsetTarget) {
    self.tableView.frame = CGRectMake(0.0f,
                      offsetTarget - point.y,
                      frame.size.width,
                      frame.size.height);
} else {
    self.tableView.frame = CGRectMake(0.0f,
                      0.0f,
                      frame.size.width,
                      frame.size.height);
}

[UIView commitAnimations];

return YES;

}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];

[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

CGRect frame = self.tableView.frame;
self.tableView.frame = CGRectMake(0.0f,
                  0.0f,
                  frame.size.width,
                  frame.size.height);

[UIView commitAnimations];

return YES;

}

3 голосов
/ 12 января 2018

Swift 4.2 полное решение

Я создал GIST с набором протоколов , который упрощает работу с добавлением дополнительного пространства при отображении, скрытии или изменении клавиатуры.

Особенности

  • Корректно работает с изменениями рамки клавиатуры (например, изменения высоты клавиатуры, такие как emojii → нормальная клавиатура).
  • Поддержка TabBar и ToolBar для примера UITableView (в других примерах вы получаете неправильные вставки).
  • Продолжительность динамической анимации (не жестко запрограммирована).
  • Протоколно-ориентированный подход, который можно легко изменить для ваших целей.

Использование

Пример базового использования в контроллере представления, который содержит некоторое представление прокрутки (представление таблицы также поддерживается).

class SomeViewController: UIViewController {
  @IBOutlet weak var scrollView: UIScrollView!

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    addKeyboardFrameChangesObserver()
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    removeKeyboardFrameChangesObserver()
  }
}

extension SomeViewController: ModifableInsetsOnKeyboardFrameChanges {
  var scrollViewToModify: UIScrollView { return scrollView }
}

Ядро: наблюдатель смены кадров

Протокол KeyboardChangeFrameObserver будет вызывать событие каждый раз при смене кадра клавиатуры (включая отображение, скрытие, смену кадра).

  1. Позвоните addKeyboardFrameChangesObserver() по viewWillAppear() или аналогичному методу.
  2. Звоните removeKeyboardFrameChangesObserver() по viewWillDisappear() или аналогичным методам.

Реализация: просмотр с прокруткой

Протокол

ModifableInsetsOnKeyboardFrameChanges добавляет поддержку UIScrollView в основной протокол. Изменяет вставки вида прокрутки при смене рамки клавиатуры.

Вашему классу нужно установить вид прокрутки, вставки будут увеличиваться / уменьшаться при смене кадров клавиатуры.

var scrollViewToModify: UIScrollView { get }
3 голосов
/ 28 февраля 2009

Поскольку у вас есть текстовые поля в таблице, лучший способ на самом деле - изменить размер таблицы - вам нужно установить меньшую высоту tableView.frame на размер клавиатуры (я думаю, около 165 пикселей) и затем развернуть это снова, когда клавиатура отклонена.

При желании вы также можете отключить взаимодействие с пользователем для tableView, если вы не хотите, чтобы пользователь прокручивал.

...