题 当键盘存在时,如何让UITextField向上移动?


使用iOS SDK:

我有一个 UIView 同 UITextField带来键盘的。我需要它能够:

  1. 允许滚动内容 UIScrollView 在键盘启动后查看其他文本字段

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

我知道我需要一个 UIScrollView。我试过改变我的班级 UIView 到了 UIScrollView 但我仍然无法向上或向下滚动文本框。

我需要两个吗? UIView 和a UIScrollView?一个人进去另一个吗?

需要实现什么才能自动滚动到活动文本字段?

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

注意: UIView (要么 UIScrollView我正在与之合作的是一个tabbar(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
}

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


1558


起源


看一下这个。没有麻烦你。 TPKeyboardAvoiding - ArunaLK
它由Apple记录,我认为这是最好的方式: developer.apple.com/library/ios/#documentation/StringsTextFonts/... - Maik639
使用此代码。您只需在appdelegate.m文件中使用1行即可。 github.com/hackiftekhar/IQKeyboardManager - Pradeep Mittal
到目前为止,我找到的最好的方法是这个开源 TPKeyboardAvoiding - Mongi Zaidi
另一种方法是在TableViewController中添加这样的内容文本字段,并让tableview处理这个。 - Vicky Dhas


答案:


  1. 你只需要一个 ScrollView 如果你现在的内容不适合iPhone屏幕。 (如果你要添加 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];
}

971



_textField是什么?我把它复制到我的代码中,它说_textField是未声明的。 - Cocoa Dev
这是你用来说“当用户在这里编辑时视图应该向上滑动”或者其他东西的字段......但是如果你有更多字段,你可以删除它。 - patrick
如果您支持主视图的旋转,则不是特别有用。 - FiddleMeRagged
为了使这项工作,我不得不评论出来 textFieldDidBeginEditing 部分。 - avance
使用硬编码键盘大小不好。 - Jonny


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

如果您有以下几点需要考虑 UIScrollView 没有正确滚动。

1)确保您的contentSize大于 UIScrollView 框架大小。了解的方式 UIScrollViews 是那个 UIScrollView 就像在contentSize中定义的内容上的查看窗口。所以当为了 UIScrollview 要在任何地方滚动,contentSize必须大于 UIScrollView。否则,不需要滚动,因为contentSize中定义的所有内容都已可见。 BTW,默认contentSize = CGSizeZero

2)现在你明白了 UIScrollView 实际上是一个进入“内容”的窗口,确保键盘不会遮挡您的内容 UIScrollView's 查看“窗口”将调整大小 UIScrollView 所以,当键盘存在时,你有 UIScrollView 窗口大小只是原始的 UIScrollView frame.size.height减去键盘的高度。这将确保您的窗口只是那个小的可视区域。

3)这是一个问题:当我第一次实现这个时,我认为我必须得到它 CGRect 已编辑的文本字段和调用 UIScrollView's scrollRecToVisible方法。我实施了 UITextFieldDelegate 方法 textFieldDidBeginEditing 打电话给 scrollRecToVisible 方法。这实际上有一个奇怪的副作用,滚动会  该 UITextField 到位。在最长的时间里,我无法弄清楚它是什么。然后我评论了 textFieldDidBeginEditing 委托方法,一切正常!!(???)。事实证明,我相信 UIScrollView 实际上隐含地带来了当前编辑过的 UITextField 隐含地进入可视窗口。我的执行情况 UITextFieldDelegate 方法和随后的调用 scrollRecToVisible 是多余的,是造成奇怪副作用的原因。

所以这里是正确滚动你的步骤 UITextField 在一个 UIScrollView 键盘出现时就位。

// 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. 取消注册键盘的nofitications at viewDidUnload
  3. 确保 contentSize 已设置且大于您的 UIScrollView 在 viewDidLoad
  4. 收缩 该 UIScrollView 当键盘存在时
  5. 还原 该 UIScrollView 当键盘消失。
  6. 使用ivar检测键盘是否已经显示在屏幕上,因为每次发送键盘通知都是如此 UITextField 即使键盘已经存在也要有标签以避免 萎缩 该 UIScrollView 什么时候已经 压缩

有一点需要注意的是 UIKeyboardWillShowNotification 当你在另一个键盘上键盘时,即使键盘已经在屏幕上也会触发 UITextField。我通过使用伊娃来照顾这个,以避免重新调整 UIScrollView 当键盘已经在屏幕上。无意中调整了大小 UIScrollView 当键盘已经存在将是灾难性的!

希望这段代码可以让你们中的一些人头疼。


437



很好,但有两个问题:1。 UIKeyboardBoundsUserInfoKey 已弃用。 2. keyboardSize位于“屏幕坐标”中,因此如果旋转或缩放帧,则viewFrame计算将失败。 - Martin Wickman
@Martin Wickman - 使用 CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; 而不是弃用 UIKeyboardBoundsUserInfoKey - sottenad
嗨,我做了同样的事情,但是当用户开始输入时,文本视图才会向上移动?这是预期的行为还是我遗失了什么?
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size 应该 [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size 。很棒的解决方案! - j7nn7k
我喜欢你的解决方案,但我想我可以让它变得更简单:不要理会Notification Observer的东西;而是在适当的委托方法中调用正确的动画例程 - 对于UITextView,它们是textViewDidBeginEditing和textViewDidEndEditing。 - AlexChaffee


实际上,最好只使用Apple的实现,如下所述 文档。但是,他们提供的代码是错误的。替换中找到的部分 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)他们总是计算点是否在视图的框架内,但它是a ScrollView,所以它可能已经滚动,你需要考虑该偏移:

origin.y -= scrollView.contentOffset.y

(2)他们将contentOffset移动键盘的高度,但我们想要相反(我们想要移动 contentOffset 通过屏幕上可见的高度,而不是什么不是):

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

260



在滚动视图未填满屏幕的情况下,应将aRect设置为滚动视图的框架 - mblackwell8
您不应该想要CGPoint origin = activeField.frame.origin + activeField.frame.size.height吗?,因为您希望显示整个文本字段,如果它恰好只有一些像素可见,那么代码将不会进入条件。 - htafoya
此解决方案在横向方向不起作用 - 文本字段飞离视图端口的顶部。带iOS 7.1的iPad。 - Andrew
为了更好的iOS 8支持,我建议使用 UIKeyboardFrameEndUserInfoKey 代替 UIKeyboardFrameBeginUserInfoKey 在获取键盘大小时,这会拾取诸如自定义键盘更改和切换预测文本开/关等内容。 - Endareth
@Egor:你的修复使它更好地工作 - 但最后一行必须是反向的: self.scrollView.contentOffset = self.currentSVoffset; - Morten Holmgaard


textFieldDidBeginEditting 并在 textFieldDidEndEditing 调用该函数 [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)
    }

238



为什么不用 [UIView animateWithDuration: animations:^{ }]; ? - André Cytryn
这很好用,虽然const int movementDistance = -130; //根据需要调整需要更改为更灵活 - Hammer
在小型实现上非常简单。不要乱用ScrollViews和不明确的自动布局问题。 - James Perih
嗯......你根本不使用textField参数。为什么然后将它作为函数参数?此外,您还可以在Swift中使用三元运算符。使代码不那么说话。 - stk
如果视图的背景颜色不是黑色,请确保将窗口的颜色设置为与视图匹配,以便用户不会在其后面看到。即self.window.backgroundColor = [UIColor whiteColor]; - bvmobileapps


只需使用TextFields:

1a)使用 Interface Builder:选择所有TextFields =>编辑=>嵌入=> ScrollView

1b)在UIScrollView中手动嵌入名为scrollView的TextFields

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

133



但它也会提升观点 textField 已经可见了。 - TheTiger
需要改变 CGPointMake(0, textField.frame.origin.y);至 CGPointMake(0, textField.frame.origin.y + scrollView.contentInset.top); - Egor T
@Egor即使在您的评论之后它也不起作用。就像提到的“TheTiger”一样,即使文本字段可见,它也会向上移动视图。 - rak appdev


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

enter image description here

步骤1:- 我添加了全球通知 UITextFieldUITextView,和 UIKeyboard 在单身课堂上。我叫它 IQKeyboardManager

第2步:- 如果找到了 UIKeyboardWillShowNotificationUITextFieldTextDidBeginEditingNotification 要么 UITextViewTextDidBeginEditingNotification 通知,我试着去 topMostViewController 来自的例子 UIWindow.rootViewController 层次结构。为了正确发现 UITextField/UITextView 在上面, topMostViewController.view的框架需要调整。

第三步: -  我计算了预期的移动距离 topMostViewController.view 关于第一个回应 UITextField/UITextView

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

第五步: -  如果找到了 UIKeyboardWillHideNotificationUITextFieldTextDidEndEditingNotification 要么 UITextViewTextDidEndEditingNotification 通知,我再次尝试得到 topMostViewController 来自的例子 UIWindow.rootViewController 层次结构。

第六步: -  我计算了扰动的距离 topMostViewController.view 需要恢复到原来的位置。

第七步: -  我恢复了 topMostViewController.view.frame 根据不安的距离下来。

第八步: -  我实例化了单身人士 IQKeyboardManager app app上的类实例,所以每一个 UITextField/UITextView 在应用程序中将根据预期的移动距离自动调整。

就这样 IQKeyboardManager 为你做的 没有代码 真!!只需要将相关的源文件拖放到项目中。 IQKeyboardManager 也支持 设备方向自动UIToolbar管理KeybkeyboardDistanceFromTextField 比你想象的要多得多。


106



将IQKeyBoardManagerSwift目录添加到我的项目中并且不起作用。无法启用cuz在AppDelegate中无法识别... - user3722523
这感觉就像网络钓鱼,实际的解决方案没有显示,但我们看到了这个人GitHub帐户的商业广告。 - Brian


我把一个普遍的,插入式的 UIScrollViewUITableView 乃至 UICollectionView 负责将其中的所有文本字段移出键盘的子类。

当键盘即将出现时,子类将找到即将被编辑的子视图,并调整其帧和内容偏移以确保视图可见,并使用与键盘弹出窗口匹配的动画。当键盘消失时,它会恢复其先前的大小。

它应该基本上适用于任何设置,a UITableView基于界面的界面,或由手动放置的视图组成的界面。

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


99



就是这个!这是最好,最有效,最完美的解决方案!它还可以正确处理滚动视图的旋转。如果旋转一定要垂直自动调整,但不要固定在底部。我在我的案例中向滚动视图添加了一个UITextView。谢谢串! - Christopher
非常好的工作!当然,我懒得使用你的解决方案而不是DIY,但我的老板更开心,所以是的!即使有人确实想要自己做,我也喜欢你的子类方法,而不是向每个控制器添加代码。我很震惊iOS默认不像Android那样做 - 然后再次,我发现iOS和MacOS中缺少很多东西:( - eselk
昨天我花了整整一天试图获得其他一个工作的答案(试图避免添加整个库)。今天我把它插入,除了最小的工作,它工作正常。通过最小化的工作,我的意思是将我的滚动视图重命名为TPKeyboardAvoidingScrollView - learner
我的滚动视图等奇怪的东西都适合屏幕,因此无法滚动。打开和关闭键盘后,内容现在变大了(看起来像是不可见的东西被添加而不是在页面底部删除),并且可以滚动。 - Almo


对于 迅速 程序员:

这将为您完成所有工作,只需将它们放在视图控制器类中并实现 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()
}

84



SwiftLint不喜欢 self.view.frame = CGRectOffset(self.view.frame, 0, movement) 所以我改变了这一行 self.view.frame.offsetInPlace(dx: 0, dy: movement) - levibostian
Swift 4将self.view.frame = CGRectOffset(self.view.frame,0,移动)更改为self.view.frame.offsetBy(dx:0,dy:movement) - Asinox


已经有很多答案,但仍然没有上述解决方案具有“完美”无错误,向后兼容和无闪烁动画所需的所有花哨的定位功能。 (动画帧/边界和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];
}

61



@Shiun的重大改进回答。但是在键盘消失后,视图不会回到第一个位置。这仍然是一项伟大的工作:) - Lucien
谢谢,这是2017年我的最佳解决方案。请注意,您不需要自己跟踪focusedControl,您可以确定 UIApplication.shared.sendAction(...)。这是您的答案的Swift 3版本(减去willHide部分),以及 sendAction 实现: gist.github.com/xaphod/7aab1302004f6e933593a11ad8f5a72d - xaphod
@xaphod在我的情况下我需要关注更多的控件 - 例如输入字段下方的按钮。但是,现在代码已经有4年了,可能会从改进中受益。 - Martin Ullrich