Как программно перемещать UIScrollView для фокусировки в контроле над клавиатурой?

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

Это не удобно.

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

Ответ 1

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

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];

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

Ответ 2

Вот что сработало для меня. Наличие переменной экземпляра, которая содержит значение смещения 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;
}

Ответ 3

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

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

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

Здесь это.


(For google: TPKeyboardAvoiding, TPKeyboardAvoidingScrollView, TPKeyboardAvoidingCollectionView.)
Editor note: TPKeyboardAvoiding seems to be continually updated and fresh, as of 2014.

Ответ 4

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

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

Ответ 5

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

- (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];
}

Ответ 6

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

Здесь мое решение, которое, надеюсь, заставит вас потратить немного времени на это.

My 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);
}

Как только это будет сделано, нам нужно обработать событие show. Он вызывается перед вызовом 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 no overlap, there 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];
}

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

- (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

Ответ 7

Я знаю, что это старо, но все же ни один из вышеперечисленных решений не обладал всеми необходимыми позиционирующими вещами, необходимыми для этой "идеальной" без ошибок, обратной совместимости и без мерцания.
Позвольте мне поделиться своим решением (при условии, что вы настроили 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];
}

Ответ 8

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


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

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

enter image description here

(2) постройте свою красивую форму в IB. добавьте UIScrollView. разместить элементы формы ВНУТРИ ПРОСМОТРА просмотра. (Примечание. чрезвычайно полезный совет относительно конструктора интерфейса: fooobar.com/questions/69169/...)

enter image description here

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

enter image description here

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

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

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

Ответ 9

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

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 для сохранения contentoffset scrollview перед показом keyboar. Затем мы прокручиваем содержимое для y около 180 (высота брелка). когда вы коснетесь возврата в брелка, мы прокручиваем содержимое до старой точки (это contentOffset). Если у вас много текстового поля, вы не трогаете возврат в keyboar, но вы касаетесь другого текстового поля, оно будет +180. Итак, у нас есть проверка касания return

Ответ 10

Я изменил некоторые из вышеперечисленных решений, чтобы упростить их понимание и использование. Я использовал IBOutlet, чтобы несколько текстовых полей могли ссылаться на функцию с "Редактирование началось" из "Отправленные события" текстовых полей. ** Не забудьте открыть розетку для просмотра прокрутки

- (IBAction)moveViewUpwards:(id)sender
{
    CGRect rc = [sender convertRect:[sender bounds] toView:scrollView];
    rc.origin.x = 0 ;
    rc.origin.y -= 60 ;

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

}

Ответ 11

Используйте любой из них,

CGPoint bottomOffset = CGPointMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height);

[self.MainScrollView setContentOffset:bottomOffset animated:YES];

или

[self.MainScrollView scrollRectToVisible:CGRectMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height-30, MainScrollView.frame.size.width, MainScrollView.frame.size.height) animated:YES];

Ответ 12

Я думаю, что лучше использовать уведомления клавиатуры, потому что вы не знаете, является ли первый ответчик (с акцентом на управление) текстовым полем или текстовым (или любым другим). Итак, juste создайте категорию, чтобы найти первого ответчика:

#import "UIResponder+FirstResponder.h"

static __weak id currentFirstResponder;

@implementation UIResponder (FirstResponder)

+(id)currentFirstResponder {
   currentFirstResponder = nil;
   [[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:nil forEvent:nil];
   return currentFirstResponder;
}

-(void)findFirstResponder:(id)sender {
   currentFirstResponder = self;
}

@end

то

-(void)keyboardWillShowNotification:(NSNotification*)aNotification{

    contentScrollView.delegate=nil;
    contentScrollView.scrollEnabled=NO;
    contentScrollViewOriginalOffset = contentScrollView.contentOffset;

    UIResponder *lc_firstResponder = [UIResponder currentFirstResponder];
    if([lc_firstResponder isKindOfClass:[UIView class]]){

        UIView *lc_view = (UIView *)lc_firstResponder;

        CGRect lc_frame = [lc_view convertRect:lc_view.bounds toView:contentScrollView];
        CGPoint lc_point = CGPointMake(0, lc_frame.origin.y-lc_frame.size.height);
        [contentScrollView setContentOffset:lc_point animated:YES];
    }
}

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

-(void)keyboardWillHideNotification:(NSNotification*)aNotification{

    contentScrollView.delegate=self;
    contentScrollView.scrollEnabled=YES;
    [contentScrollView setContentOffset:contentScrollViewOriginalOffset animated:YES];
}

Ответ 13

В 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)
    }

}