Как программно переместить UIScrollView для фокусировки в элементе управления над клавиатурой? - PullRequest
49 голосов
/ 27 января 2009

У меня 6 UITextFields на моем UIScrollView. Теперь я могу прокрутить по запросу пользователя. Но когда появляется клавиатура, некоторые текстовые поля скрыты.

Это не удобно для пользователя.

Как программно прокрутить вид, чтобы убедиться, что клавиатура не скрывает текстовое поле?

Ответы [ 13 ]

77 голосов
/ 18 марта 2010

Вот что сработало для меня. Наличие переменной экземпляра, которая содержит значение смещения UIScrollView до настройки представления для клавиатуры , поэтому вы можете восстановить предыдущее состояние после того, как UITextField вернет:

//header
@interface TheViewController : UIViewController <UITextFieldDelegate> {
    CGPoint svos;
}


//implementation
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    svos = scrollView.contentOffset;
    CGPoint pt;
    CGRect rc = [textField bounds];
    rc = [textField convertRect:rc toView:scrollView];
    pt = rc.origin;
    pt.x = 0;
    pt.y -= 60;
    [scrollView setContentOffset:pt animated:YES];           
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [scrollView setContentOffset:svos animated:YES]; 
    [textField resignFirstResponder];
    return YES;
}
38 голосов
/ 03 февраля 2009

Наконец, простое исправление:

UIScrollView* v = (UIScrollView*) self.view ;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:v];
rc.origin.x = 0 ;
rc.origin.y -= 60 ;

rc.size.height = 400;
[self.scroll scrollRectToVisible:rc animated:YES];

Теперь я думаю, это только объединить это с ссылкой выше и установлено!

22 голосов
/ 12 апреля 2011

Я собрал универсальный, выпадающий подкласс UIScrollView и UITableView, который заботится о перемещении всех текстовых полей в нем из клавиатуры.

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

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

Здесь это.


(For google: TPKeyboardAvoiding, TPKeyboardAvoidingScrollView, TPKeyboardAvoidingCollectionView.)
Editor's note: TPKeyboardAvoiding seems to be continually updated and fresh, as of 2014.
2 голосов
/ 23 июня 2014

простой и лучший

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

  // self.scrlViewUI.contentOffset = CGPointMake(0, textField.frame.origin.y);
    [_scrlViewUI setContentOffset:CGPointMake(0,textField.center.y-90) animated:YES];
    tes=YES;
    [self viewDidLayoutSubviews];
}
2 голосов
/ 28 января 2009

Если вы установите delegate ваших текстовых полей для объекта контроллера в вашей программе, вы можете иметь этот объект для реализации методов textFieldDidBeginEditing: и textFieldShouldReturn:. Первый метод затем можно использовать для прокрутки к текстовому полю, а второй метод можно использовать для прокрутки назад.

Вы можете найти код, который я использовал для этого в моем блоге: Сдвиг UITextViews вокруг, чтобы избежать клавиатуры . Я не проверял этот код для текстовых представлений в UIScrollView, но он должен работать.

1 голос
/ 29 января 2015

Это мой код, надеюсь, он вам поможет. Работает нормально, если у вас много текстовых полей

CGPoint contentOffset;
bool isScroll;
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    contentOffset = self.myScroll.contentOffset;
    CGPoint newOffset;
    newOffset.x = contentOffset.x;
    newOffset.y = contentOffset.y;
    //check push return in keyboar
    if(!isScroll){
        //180 is height of keyboar
        newOffset.y += 180;
        isScroll=YES;
    }
   [self.myScroll setContentOffset:newOffset animated:YES];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField{
    //reset offset of content
    isScroll = NO;
    [self.myScroll setContentOffset:contentOffset animated:YES];
    [textField endEditing:true];
    return  true;
}

у нас есть пункт contentOffset, чтобы сохранить смещение содержимого просмотра прокрутки перед показом клавиатуры. Затем мы прокручиваем контент примерно на 180 градусов (высота клавишной панели). когда вы коснетесь возврата в keyboar, мы прокрутим содержимое до старой точки (это contentOffset). Если у вас много текстовых полей, вы не касаетесь возврата в клавиатуре, но вы касаетесь другого текстового поля, оно будет +180. Итак, у нас есть проверка касания возврата

1 голос
/ 31 марта 2014

Вы можете проверить это: https://github.com/michaeltyson/TPKeyboardAvoiding (я использовал этот образец для своих приложений). Это работает так хорошо. Я надеюсь, что это поможет вам.


Собственно, вот полное руководство по использованию TPKeyboardAvoiding, которое может помочь кому-то

(1) загрузите zip-файл по ссылке github. добавьте эти четыре файлы в ваш проект Xcode:

enter image description here

(2) создай свою прекрасную форму в IB. добавить UIScrollView. сидеть элементы формы ВНУТРИ вида прокрутки . (Примечание - чрезвычайно полезный совет относительно построителя интерфейса: https://stackoverflow.com/a/16952902/294884)

enter image description here

(3) нажмите в представлении прокрутки . затем справа вверху, третья кнопка, вы увидите слово «UIScrollView». используя копирование и вставку, измените его на"TPKeyboardAvoidingScrollView"

enter image description here

(4) вот и все. поместите приложение в магазин приложений и выставьте счет своему клиенту.

(Кроме того, просто нажмите на вкладку «Инспектор» в представлении прокрутки. Вы можете включить или отключить подпрыгивание и полосы прокрутки - ваши предпочтения.)

Личный комментарий - я настоятельно рекомендую использовать представление прокрутки (или представление коллекции) для форм ввода почти во всех случаях. не использовать табличное представление. это проблематично по многим причинам. и довольно просто, это невероятно проще использовать представление прокрутки. просто выложи как хочешь. это 100% Wysiwyg в конструкторе интерфейсов. надеюсь, это поможет

1 голос
/ 01 марта 2013

Я знаю, что это старая версия, но до сих пор ни в одном из вышеприведенных решений не было всего такого фантастического позиционирования, необходимого для этой "идеальной" безошибочной, обратной совместимости и без мерцания анимации.
Позвольте мне поделиться своим решением (при условии, что вы настроили UIKeyboardWill(Show|Hide)Notification):

// Called when UIKeyboardWillShowNotification is sent
- (void)keyboardWillShow:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = [notification userInfo];

    CGRect keyboardFrameInWindow;
    [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameInWindow];

    // the keyboard frame is specified in window-level coordinates. this calculates the frame as if it were a subview of our view, making it a sibling of the scroll view
    CGRect keyboardFrameInView = [self.view convertRect:keyboardFrameInWindow fromView:nil];

    CGRect scrollViewKeyboardIntersection = CGRectIntersection(_scrollView.frame, keyboardFrameInView);
    UIEdgeInsets newContentInsets = UIEdgeInsetsMake(0, 0, scrollViewKeyboardIntersection.size.height, 0);

    // this is an old animation method, but the only one that retains compaitiblity between parameters (duration, curve) and the values contained in the userInfo-Dictionary.
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    _scrollView.contentInset = newContentInsets;
    _scrollView.scrollIndicatorInsets = newContentInsets;

    /*
     * Depending on visual layout, _focusedControl should either be the input field (UITextField,..) or another element
     * that should be visible, e.g. a purchase button below an amount text field
     * it makes sense to set _focusedControl in delegates like -textFieldShouldBeginEditing: if you have multiple input fields
     */
    if (_focusedControl) {
        CGRect controlFrameInScrollView = [_scrollView convertRect:_focusedControl.bounds fromView:_focusedControl]; // if the control is a deep in the hierarchy below the scroll view, this will calculate the frame as if it were a direct subview
        controlFrameInScrollView = CGRectInset(controlFrameInScrollView, 0, -10); // replace 10 with any nice visual offset between control and keyboard or control and top of the scroll view.

        CGFloat controlVisualOffsetToTopOfScrollview = controlFrameInScrollView.origin.y - _scrollView.contentOffset.y;
        CGFloat controlVisualBottom = controlVisualOffsetToTopOfScrollview + controlFrameInScrollView.size.height;

        // this is the visible part of the scroll view that is not hidden by the keyboard
        CGFloat scrollViewVisibleHeight = _scrollView.frame.size.height - scrollViewKeyboardIntersection.size.height;

        if (controlVisualBottom > scrollViewVisibleHeight) { // check if the keyboard will hide the control in question
            // scroll up until the control is in place
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y += (controlVisualBottom - scrollViewVisibleHeight);

            // make sure we don't set an impossible offset caused by the "nice visual offset"
            // if a control is at the bottom of the scroll view, it will end up just above the keyboard to eliminate scrolling inconsistencies
            newContentOffset.y = MIN(newContentOffset.y, _scrollView.contentSize.height - scrollViewVisibleHeight);

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        } else if (controlFrameInScrollView.origin.y < _scrollView.contentOffset.y) {
            // if the control is not fully visible, make it so (useful if the user taps on a partially visible input field
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y = controlFrameInScrollView.origin.y;

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        }
    }

    [UIView commitAnimations];
}


// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillHide:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = notification.userInfo;

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    // undo all that keyboardWillShow-magic
    // the scroll view will adjust its contentOffset apropriately
    _scrollView.contentInset = UIEdgeInsetsZero;
    _scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;

    [UIView commitAnimations];
}
1 голос
/ 21 августа 2012

Ответы, опубликованные до сих пор, не сработали для меня, так как у меня достаточно глубокая вложенная структура UIViews. Кроме того, у меня была проблема, что некоторые из этих ответов работали только на определенных ориентациях устройства.

Вот мое решение, которое, мы надеемся, заставит вас тратить на это меньше времени.

Мой UIViewTextView является производным от UIView, является делегатом UITextView и добавляет UITextView после считывания некоторых параметров из файла XML для этого UITextView (эта XML-часть здесь опущена для ясности).

Вот определение частного интерфейса:

#import "UIViewTextView.h"
#import <CoreGraphics/CoreGraphics.h>
#import <CoreGraphics/CGColor.h>

@interface UIViewTextView (/**/) {
  @private
  UITextView *tf;

  /*
   * Current content scroll view
   * position and frame
   */
  CGFloat currentScrollViewPosition;
  CGFloat currentScrollViewHeight;
  CGFloat kbHeight;
  CGFloat kbTop;

  /*
   * contentScrollView is the UIScrollView
   * that contains ourselves.
   */
  UIScrollView contentScrollView;
}
@end

В методе init мне нужно зарегистрировать обработчики событий:

@implementation UIViewTextView

- (id) initWithScrollView:(UIScrollView*)scrollView {
  self              = [super init];

  if (self) {
    contentScrollView = scrollView;

    // ...

    tf = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 241, 31)];

    // ... configure tf and fetch data for it ...

    tf.delegate = self;

    // ...

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(keyboardWasShown:) name: UIKeyboardWillShowNotification object:nil];
    [nc addObserver:self selector:@selector(keyboardWasHidden:) name: UIKeyboardWillHideNotification object:nil];
    [self addSubview:tf];
  }

  return(self);
}

Как только это будет сделано, нам нужно обработать событие показа клавиатуры. Это вызывается до вызова textViewBeginEditing, поэтому мы можем использовать его для определения некоторых свойств клавиатуры. По сути, мы хотим знать высоту клавиатуры. Это, к сожалению, должно быть взято из его свойства width в ландшафтном режиме:

-(void)keyboardWasShown:(NSNotification*)aNotification {
  NSDictionary* info                 = [aNotification userInfo];
  CGRect kbRect                      = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
  CGSize kbSize                      = kbRect.size;

  CGRect screenRect                  = [[UIScreen mainScreen] bounds];
  CGFloat sWidth                     = screenRect.size.width;
  CGFloat sHeight                    = screenRect.size.height;

  UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];

  if ((orientation == UIDeviceOrientationPortrait)
      ||(orientation == UIDeviceOrientationPortraitUpsideDown)) {
    kbHeight     = kbSize.height;
    kbTop        = sHeight - kbHeight;
  } else {
    //Note that the keyboard size is not oriented
    //so use width property instead
    kbHeight     = kbSize.width;
    kbTop        = sWidth - kbHeight;
  }

Далее нам нужно прокрутить, когда мы начнем редактирование. Мы делаем это здесь:

- (void) textViewDidBeginEditing:(UITextView *)textView {
  /*
   * Memorize the current scroll position
   */
  currentScrollViewPosition = contentScrollView.contentOffset.y;

  /*
   * Memorize the current scroll view height
   */
  currentScrollViewHeight   = contentScrollView.frame.size.height;

  // My top position
  CGFloat myTop    = [self convertPoint:self.bounds.origin toView:[UIApplication sharedApplication].keyWindow.rootViewController.view].y;

  // My height
  CGFloat myHeight = self.frame.size.height;

  // My bottom
  CGFloat myBottom = myTop + myHeight;

  // Eventual overlap
  CGFloat overlap  = myBottom - kbTop;

  /*
   * If there's no overlap, there's nothing to do.
   */
  if (overlap < 0) {
    return;
  }

  /*
   * Calculate the new height
   */
  CGRect crect = contentScrollView.frame;
  CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight + overlap);

  /*
   * Set the new height
   */
  [contentScrollView setFrame:nrect];

  /*
   * Set the new scroll position
   */
  CGPoint npos;

  npos.x = contentScrollView.contentOffset.x;
  npos.y = contentScrollView.contentOffset.y + overlap;

  [contentScrollView setContentOffset:npos animated:NO];
}

Когда мы заканчиваем редактирование, мы делаем это для сброса положения прокрутки:

- (void) textViewDidEndEditing:(UITextView *)textView {
  /*
   * Reset the scroll view position
   */
  CGRect crect = contentScrollView.frame;
  CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight);

  [contentScrollView setFrame:nrect];

  /*
   * Reset the scroll view height
   */
  CGPoint npos;

  npos.x = contentScrollView.contentOffset.x;
  npos.y = currentScrollViewPosition;

  [contentScrollView setContentOffset:npos animated:YES];
  [tf resignFirstResponder];

  // ... do something with your data ...

}

Больше ничего не оставалось делать на клавиатуре, был скрытый обработчик событий; в любом случае мы оставляем это:

-(void)keyboardWasHidden:(NSNotification*)aNotification {
}

И это все.

/*
   // Only override drawRect: if you perform custom drawing.
   // An empty implementation adversely affects performance during animation.
   - (void)drawRect:(CGRect)rect
   {
   // Drawing code
   }
 */

@end
0 голосов
/ 10 марта 2016

В Swift 1.2+ сделайте что-то вроде этого:

class YourViewController: UIViewController, UITextFieldDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        _yourTextField.delegate = self //make sure you have the delegate set to this view controller for each of your textFields so textFieldDidBeginEditing can be called for each one
        ...

    }

    func textFieldDidBeginEditing(textField: UITextField) {
        var point = textField.convertPoint(textField.frame.origin, toView: _yourScrollView)
        point.x = 0.0 //if your textField does not have an origin at 0 for x and you don't want your scrollView to shift left and right but rather just up and down
        _yourScrollView.setContentOffset(point, animated: true)
    }

    func textFieldDidEndEditing(textField: UITextField) {
        //Reset scrollview once done editing
        scrollView.setContentOffset(CGPoint.zero, animated: true)
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...