it-swarm.com.ru

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

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

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

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

49
mamcx

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

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

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

38
mamcx

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

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

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

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

Здесь это так.


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

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

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

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

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

2
Matt Gallagher

Ответы, опубликованные до сих пор, не сработали для меня, так как у меня достаточно глубокая структура 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
1
Matthias Nott

Вы можете проверить это: 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
hightech

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

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

1
Heo Đất Hades

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

Я изменил некоторые из приведенных выше решений, чтобы их было легче понять и использовать. Я использовал 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];

}
0
pdpAxis

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

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

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

}
0
ColossalChris

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

#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];
}
0
Bejil