当有键盘时如何在开始编辑时使 UITextField 向上移动?

使用 iOS SDK:

我有一个带有UITextFieldUIViewUITextField一个键盘。我需要它能够:

  1. 提起键盘后,允许滚动UIScrollView的内容以查看其他文本字段

  2. 自动 “跳跃”(通过向上滚动)或缩短

我知道我需要一个UIScrollView 。我尝试将UIView的类更改为UIScrollView但仍然无法向上或向下滚动文本框。

我是否需要UIViewUIScrollView ?一个人会进入另一个人吗?

为了自动滚动到活动文本字段,需要实现什么?

理想情况下,将在 Interface Builder 中完成尽可能多的组件设置。我只想编写所需的代码。

注意:我正在使用的UIView (或UIScrollView )由一个标签栏( UITabBar )调出,该UITabBar需要正常运行。


编辑:我只是在键盘出现时添加滚动条。即使不需要它,我也觉得它提供了一个更好的界面,因为例如,用户可以滚动和更改文本框。

我可以在键盘上下移动时更改UIScrollView的帧大小的地方工作。我只是在使用:

-(void)textFieldDidBeginEditing:(UITextField *)textField { 
    //Keyboard becomes visible
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
                     scrollView.frame.origin.y, 
scrollView.frame.size.width,
scrollView.frame.size.height - 215 + 50);   //resize
}

-(void)textFieldDidEndEditing:(UITextField *)textField {
   //keyboard will hide
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
       scrollView.frame.origin.y, 
     scrollView.frame.size.width,
      scrollView.frame.size.height + 215 - 50); //resize
}

但是,这不会自动 “向上移动” 或将下部文本字段居中显示区域的中心,这是我真正想要的。

答案

  1. 如果您现在的内容不适合 iPhone 屏幕,则只需要ScrollView 。 (如果要添加ScrollView作为组件的超级视图。只是为了在键盘出现时使TextField向上滚动,则不需要这样做。)

  2. 为了在不被键盘隐藏的情况下显示textfields ,标准方法是在显示键盘时上下移动具有文本字段的视图。

这是一些示例代码:

#define kOFFSET_FOR_KEYBOARD 80.0

-(void)keyboardWillShow {
    // Animate the current view out of the way
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)keyboardWillHide {
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
    if ([sender isEqual:mailTf])
    {
        //move the main view, so that the keyboard does not hide it.
        if  (self.view.frame.origin.y >= 0)
        {
            [self setViewMovedUp:YES];
        }
    }
}

//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.3]; // if you want to slide up the view

    CGRect rect = self.view.frame;
    if (movedUp)
    {
        // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 
        // 2. increase the size of the view so that the area behind the keyboard is covered up.
        rect.origin.y -= kOFFSET_FOR_KEYBOARD;
        rect.size.height += kOFFSET_FOR_KEYBOARD;
    }
    else
    {
        // revert back to the normal state.
        rect.origin.y += kOFFSET_FOR_KEYBOARD;
        rect.size.height -= kOFFSET_FOR_KEYBOARD;
    }
    self.view.frame = rect;

    [UIView commitAnimations];
}


- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

对于由多个UITextFields组成的UIScrollView ,我也遇到很多问题,其中的一个或多个在编辑时会被键盘遮盖。

如果您的UIScrollView滚动不正确,则需要考虑以下事项。

1)确保您的 contentSize 大于UIScrollView框架的大小。理解UIScrollViews的方法是UIScrollView就像 contentSize 中定义的内容的查看窗口。因此,当UIScrollview滚动到任何位置时,contentSize 必须大于UIScrollView 。否则,不需要滚动,因为 contentSize 中定义的所有内容都已经可见。顺便说一句,默认 contentSize = CGSizeZero

2)现在您已经知道UIScrollView确实是进入 “内容” 的窗口,确保键盘不会遮挡UIScrollView's查看 “窗口” 的方法是调整UIScrollView大小,以便在存在键盘时,将UIScrollView窗口的大小调整为仅原始UIScrollView frame.size.height 减去键盘的高度。这将确保您的窗口仅是一个较小的可见区域。

3)这就是要注意的问题:第一次实现此功能时,我认为我必须获取已编辑文本字段的CGRect并调用UIScrollView's scrollRecToVisible 方法。我通过调用scrollRecToVisible方法实现了UITextFieldDelegate方法textFieldDidBeginEditing 。这实际上与一个奇怪的副作用是滚动将其卡工作UITextField到位。在最长的时间内,我不知道那是什么。然后我注释掉了textFieldDidBeginEditing Delegate 方法,并且一切正常!(???)。事实证明,我相信UIScrollView实际上隐式地将当前编辑的UITextField隐式UITextField入可见窗口。我对UITextFieldDelegate方法的实现以及随后对scrollRecToVisible调用是多余的,并且是造成奇怪副作用的原因。

因此,这是在键盘出现时正确滚动UIScrollViewUITextField到位的步骤。

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad 
{
    [super viewDidLoad];

    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillShow:) 
                                                 name:UIKeyboardWillShowNotification 
                                               object:self.view.window];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillHide:) 
                                                 name:UIKeyboardWillHideNotification 
                                               object:self.view.window];
    keyboardIsShown = NO;
    //make contentSize bigger than your scrollSize (you will need to figure out for your own use case)
    CGSize scrollContentSize = CGSizeMake(320, 345);
    self.scrollView.contentSize = scrollContentSize;
}

- (void)keyboardWillHide:(NSNotification *)n
{
    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;


    // resize the scrollview
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height += (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];

    keyboardIsShown = NO;
}

- (void)keyboardWillShow:(NSNotification *)n
{
    // This is an ivar I'm using to ensure that we do not do the frame size adjustment on the `UIScrollView` if the keyboard is already shown.  This can happen if the user, after fixing editing a `UITextField`, scrolls the resized `UIScrollView` to another `UITextField` and attempts to edit the next `UITextField`.  If we were to resize the `UIScrollView` again, it would be disastrous.  NOTE: The keyboard notification will fire even when the keyboard is already shown.
    if (keyboardIsShown) {
        return;
    }

    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

    // resize the noteView
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height -= (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];
    keyboardIsShown = YES;
}
  1. viewDidLoad注册键盘通知
  2. viewDidUnload取消注册键盘键盘
  3. 确保设置了contentSize并大于viewDidLoadUIScrollView
  4. 有键盘时缩小 UIScrollView
  5. 键盘消失还原回 UIScrollView
  6. 使用伊娃如果键盘已经显示在屏幕上,以检测由于键盘通知每一次发送UITextField的标签即使键盘已经存在,以避免萎缩 UIScrollView时,它已经缩水

要注意的一件事是,即使您在另一个UITextField上进行选项卡UIKeyboardWillShowNotification时,即使键盘已经在屏幕上, UIKeyboardWillShowNotification也会触发。我通过使用 ivar 避免在键盘已经在屏幕上时调整UIScrollView大小来解决此问题。当键盘已经存在时,无意中调整UIScrollView大小将是灾难性的!

希望这段代码可以使您省去很多麻烦。

实际上最好使用docs 中提供的 Apple 的实现。但是,他们提供的代码是错误的。替换在keyboardWasShown:找到的部分keyboardWasShown:在以下注释的正下方:

NSDictionary* info = [aNotification userInfo];
CGRect keyPadFrame=[[UIApplication sharedApplication].keyWindow convertRect:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:self.view];
CGSize kbSize =keyPadFrame.size;
CGRect activeRect=[self.view convertRect:activeField.frame fromView:activeField.superview];
CGRect aRect = self.view.bounds;
aRect.size.height -= (kbSize.height);

CGPoint origin =  activeRect.origin;
origin.y -= backScrollView.contentOffset.y;
if (!CGRectContainsPoint(aRect, origin)) {
    CGPoint scrollPoint = CGPointMake(0.0,CGRectGetMaxY(activeRect)-(aRect.size.height));
    [backScrollView setContentOffset:scrollPoint animated:YES];
}

Apple 的代码存在以下问题:(1)它们始终计算该点是否在视图的框架内,但是它是ScrollView ,因此它可能已经滚动了,您需要考虑该偏移量:

origin.y -= scrollView.contentOffset.y

(2)他们将 contentOffset 按键盘的高度移动,但我们想要相反的操作(我们希望将contentOffset按屏幕上可见的高度移动,而不是不可见的高度):

activeField.frame.origin.y-(aRect.size.height)

textFieldDidBeginEdittingtextFieldDidEndEditing调用函数[self animateTextField:textField up:YES]如下所示:

-(void)textFieldDidBeginEditing:(UITextField *)textField 
{ 
    [self animateTextField:textField up:YES]; 
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField:textField up:NO];
}

-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
    const int movementDistance = -130; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? movementDistance : -movementDistance); 

    [UIView beginAnimations: @"animateTextField" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

希望这段代码对您有所帮助。

在 Swift 2

func animateTextField(textField: UITextField, up: Bool) 
{
     let movementDistance:CGFloat = -130
     let movementDuration: Double = 0.3

     var movement:CGFloat = 0
     if up 
     {
         movement = movementDistance
     }
     else 
     {
         movement = -movementDistance
     }
     UIView.beginAnimations("animateTextField", context: nil)
     UIView.setAnimationBeginsFromCurrentState(true)
     UIView.setAnimationDuration(movementDuration)
     self.view.frame = CGRectOffset(self.view.frame, 0, movement)
     UIView.commitAnimations()
}


func textFieldDidBeginEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:true)
}

func textFieldDidEndEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:false)
}

SWIFT 3

func animateTextField(textField: UITextField, up: Bool)
    {
        let movementDistance:CGFloat = -130
        let movementDuration: Double = 0.3

        var movement:CGFloat = 0
        if up
        {
            movement = movementDistance
        }
        else
        {
            movement = -movementDistance
        }
        UIView.beginAnimations("animateTextField", context: nil)
        UIView.setAnimationBeginsFromCurrentState(true)
        UIView.setAnimationDuration(movementDuration)
        self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
        UIView.commitAnimations()
    }


    func textFieldDidBeginEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:true)
    }

    func textFieldDidEndEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:false)
    }

仅使用 TextFields:

1a)使用Interface Builder :选择所有 TextFields => Edit => Embed In => ScrollView

1b)在称为 scrollView 的 UIScrollView 中手动嵌入 TextField

2)设置UITextFieldDelegate

3)设置每个textField.delegate = self; (或在Interface Builder建立连接)

4) 复制 / 粘贴:

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    CGPoint scrollPoint = CGPointMake(0, textField.frame.origin.y);
    [scrollView setContentOffset:scrollPoint animated:YES];
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    [scrollView setContentOffset:CGPointZero animated:YES];
}

对于通用解决方案 ,这是我实现IQKeyboardManager 的方法

在此处输入图片说明

步骤 1:-我在单例类中添加了UITextFieldUITextViewUIKeyboard全局通知。我称它为IQKeyboardManager

步骤 2:-如果找到UIKeyboardWillShowNotificationUITextFieldTextDidBeginEditingNotificationUITextViewTextDidBeginEditingNotification通知,我尝试从UIWindow.rootViewController层次结构中获取topMostViewController实例。为了正确地发现UITextField / UITextView ,需要调整topMostViewController.view的框架。

步骤 3:-我相对于第一个响应的UITextField / UITextView计算了topMostViewController.view预期移动距离。

步骤 4:-我根据预期的移动距离上下移动了topMostViewController.view.frame

步骤 5:-如果找到UIKeyboardWillHideNotificationUITextFieldTextDidEndEditingNotificationUITextViewTextDidEndEditingNotification通知,则我再次尝试从UIWindow.rootViewController层次结构中获取topMostViewController实例。

步骤 6:-我计算了topMostViewController.view受干扰距离,该距离需要恢复到其原始位置。

步骤 7:-我根据受干扰的距离恢复了topMostViewController.view.frame

步骤 8:-我在应用程序加载时实例化了单例IQKeyboardManager类实例,因此应用程序中的每个UITextField / UITextView都会根据预期的移动距离自动进行调整。

这就是IQKeyboardManager 真正为您完成的所有工作,而无需任何代码 !只需要将相关的源文件拖放到项目中即可。 IQKeyboardManager还支持 “ 设备方向” ,“ 自动 UIToolbar 管理” ,“ KeybkeyboardDistanceFromTextField”等功能。

我整理了一个通用的UIScrollViewUITableView甚至UICollectionView子类,该子类负责将其中的所有文本字段移出键盘。

当键盘即将出现时,子类将找到要编辑的子视图,并调整其框架和内容偏移量以确保该视图可见,并带有动画以匹配键盘弹出窗口。键盘消失后,它将恢复其先前的大小。

它基本上可以与任何设置一起使用,既可以是基于UITableView的界面,也可以是由手动放置的视图组成的界面。

这是 tis: 用于将文本字段移出键盘的解决方案

对于Swift程序员:

这将为您完成所有工作,只需将它们放在视图控制器类中,并将UITextFieldDelegate实现到视图控制器,并将 textField 的委托设置为self

textField.delegate = self // Setting delegate of your UITextField to self

实现委托回调方法:

func textFieldDidBeginEditing(textField: UITextField) {
    animateViewMoving(true, moveValue: 100)
}

func textFieldDidEndEditing(textField: UITextField) {
    animateViewMoving(false, moveValue: 100)
}

// Lifting the view up
func animateViewMoving (up:Bool, moveValue :CGFloat){
    let movementDuration:NSTimeInterval = 0.3
    let movement:CGFloat = ( up ? -moveValue : moveValue)
    UIView.beginAnimations( "animateView", context: nil)
    UIView.setAnimationBeginsFromCurrentState(true)
    UIView.setAnimationDuration(movementDuration )
    self.view.frame = CGRectOffset(self.view.frame, 0,  movement)
    UIView.commitAnimations()
}

对于 Swift 4,4.2,5:更改

self.view.frame = CGRectOffset(self.view.frame, 0,  movement)

self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)

关于此实现的最后一点说明:如果在显示键盘时将另一个视图控制器推到堆栈上,则会产生一个错误,即视图返回到其中心框架,但键盘偏移未重置。例如,您的键盘是 nameField 的第一个响应者,但是随后您按下了一个按钮,该按钮会将您的 Help View Controller 推入堆栈。要解决偏移量错误,请确保在离开视图控制器之前调用 nameField.resignFirstResponder(),并确保同时调用了 textFieldDidEndEditing 委托方法。我在 viewWillDisappear 方法中执行此操作。

已经有很多答案,但是上面的解决方案仍然没有一个具有 “完美” 的无错误,向后兼容和无闪烁动画所需的所有花哨的定位内容。 (在同时对帧 / 边界和 contentOffset 进行动画处理,不同的界面方向,iPad 拆分键盘等时发生错误)
让我分享我的解决方案:
(假设您已设置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];
}

Shiun 表示:“事实证明,我相信 UIScrollView 实际上隐式地将当前编辑的 UITextField 隐式地带到可见窗口中”,这对于 iOS 3.1.3 似乎是正确的,但对于 3.2、4.0 或 4.1 则不成立。我必须添加一个显式的 scrollRectToVisible,以使 UITextField 在 iOS> = 3.2 上可见。