当键盘出现时,我该如何使UITextField在开始编辑时向上移动?

1792

使用iOS SDK:

我有一个带有UITextFieldUIView,这些文本框会弹出键盘。 我需要它能够:

  1. 在键盘弹出后允许滚动UIScrollView的内容以查看其他文本框

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

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

我需要同时使用UIViewUIScrollView吗? 它们是否应该嵌套在一起?

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

理想情况下,尽可能多的组件设置将在接口构建器中完成。 我只想编写必要的代码。

注意:我正在使用选项卡(UITabBar)打开工作的UIView(或UIScrollView),它需要像平常一样运行。


我添加滚动条,只是为了在键盘弹出时使用。即使不需要,我觉得它提供了更好的界面,因为用户可以滚动并更改文本框等内容。

当键盘上下移动时,我已经让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
}

然而,这并不会自动将下方的文本字段“上移”或置于可见区域的中央,而这正是我真正想要的。


7
看这个。对你来说没有麻烦。TPKeyboardAvoiding - Aruna
24
这是苹果公司提供的文档,我认为这是最好的方法:http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html - user1199624
62
使用这段代码。您只需要在appdelegate.m文件中添加一行代码即可,它就能够工作。https://github.com/hackiftekhar/IQKeyboardManager - Pradeep Mittal
10
到目前为止我发现最好的方法是使用这个开源项目TPKeyboardAvoiding - Mongi Zaidi
苹果键盘管理代码的 Swift 3.0 版本在此处:https://medium.com/@abhimuralidharan/ios-swift-3-0-keyboard-management-f3c35950b806#.7u6jj6rwx - abhimuralidharan
显示剩余14条评论
99个回答

1073
  1. 如果你现有的内容在iPhone屏幕上无法容纳,那么你只需要使用ScrollView。(如果你只是为了让TextField在键盘弹起时滚动而将ScrollView添加为组件的父视图,则不需要使用它。)

  2. 防止TextField被键盘遮挡的标准方法是在键盘显示时移动视图的位置。

以下是一些示例代码:

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

3
_textField 是什么?我将它复制到我的代码中,但它显示未声明。 - Cocoa Dev
4
如果你需要支持主视图的旋转,这个并不特别有用。 - FractalDoctor
如果键盘打开,我就会进入后台,然后再回到前台,此时键盘仍然是打开的,但视图框架没有设置在旧位置。这是什么原因? - IKKA
你根本不需要粘贴-(void)viewWillAppear:(BOOL)animated方法。它只会破坏一切。 - ZviBar
2
为了使其正常工作,我不得不注释掉 textFieldDidBeginEditing 部分。 - avance
显示剩余17条评论

450

我在使用由多个UITextFields组成的UIScrollView时遇到了很多问题,其中一个或多个文本字段在编辑时会被键盘遮挡。

如果您的UIScrollView无法正确滚动,请考虑以下几点:

1)确保您的contentSize大于UIScrollView的框架大小。要理解UIScrollView,可以将其视为内容定义在contentSize中的可视窗口。因此,为了使UIScrollView能够在任何地方滚动,contentSize必须大于UIScrollView。否则,不需要滚动,因为contentSize中定义的所有内容已经可见。默认的contentSize = CGSizeZero

2)现在您了解了UIScrollView实际上是您的“内容”中的窗口,确保键盘不遮挡UIScrollView的可视“窗口”的方法是调整UIScrollView的大小,以便在键盘出现时,您将UIScrollView窗口缩小到原始的UIScrollView frame.size.height 减去键盘高度。这将确保您的窗口只有那个小的可视区域。

3)这里有一个问题:当我首次实现此功能时,我认为必须获取编辑的textfield的CGRect并调用UIScrollViewscrollRectToVisible方法。我使用了UITextFieldDelegate方法 textFieldDidBeginEditing调用scrollRectToVisible方法。这实际上可以解决问题,但会产生奇怪的副作用,即滚动会将UITextField捏合到位置。很长一段时间我都不知道是什么原因。然后我注释了textFieldDidBeginEditing委托方法,一切都正常了!(???)。事实证明,我认为UIScrollView实际上会隐式地将当前编辑的UITextField带入可视窗口。我的UITextFieldDelegate方法和后续调用scrollRectToVisible是多余的,并且导致了奇怪的副作用。

因此,在键盘出现时,以下是正确滚动UIScrollView中的UITextField的步骤:

// 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. 确保在viewDidLoad中设置contentSize大于你的UIScrollView
  4. 当键盘出现时,缩小UIScrollView
  5. 当键盘消失时,恢复UIScrollView
  6. 使用ivar来检测键盘是否已经显示在屏幕上,因为即使键盘已经存在,每次点击UITextField都会发送键盘通知,以避免在缩小UIScrollView已经缩小的情况下再次缩小

需要注意的一点是,即使当您点击另一个UITextField时键盘已经在屏幕上,UIKeyboardWillShowNotification也会触发。我通过使用ivar来避免在键盘已经在屏幕上时调整UIScrollView大小。意外地在键盘已经存在的情况下调整UIScrollView的大小会导致灾难!

希望这段代码能节省你们很多头痛。


3
好的,但有两个问题:1.UIKeyboardBoundsUserInfoKey已经被废弃了。2. keyboardSize是以“屏幕坐标”为单位的,所以如果视图框架旋转或缩放,你的viewFrame计算将会失败。 - Martin Wickman
22
请使用CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;替代过时的UIKeyboardBoundsUserInfoKey - sottenad
1
你好,我也做了同样的事情,但是只有当用户开始输入时,文本视图才会向上移动?这是预期的行为还是我漏掉了什么? - user517491
3
“[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size” 应改为 “[[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size”。不过,这是一个非常好的解决方案! - j7nn7k
1
我喜欢你的解决方案,但我认为我可以让它更简单:不必费心使用通知观察器;相反,在适当的委托方法中调用正确的动画例程--对于UITextView,它们是textViewDidBeginEditing和textViewDidEndEditing。 - AlexChaffee
显示剩余12条评论

275

最好使用苹果提供的实现,可以在文档中找到。然而,他们提供的代码有错误。将位于以下注释下方的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];
}
苹果代码的问题在于: (1) 它们总是计算点是否在视图框架内,但这是一个ScrollView,因此它可能已经滚动,你需要考虑偏移量:
origin.y -= scrollView.contentOffset.y

(2) 他们通过键盘的高度来调整 contentOffset,但我们想要相反的效果(我们希望将 contentOffset 按照屏幕上可见的高度来调整,而不是看不见的部分):

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

3
当滚动视图未填满屏幕时,应将aRect设置为滚动视图的框架。 - mblackwell8
2
你不应该想要 CGPoint origin = activeField.frame.origin + activeField.frame.size.height 吗?因为你希望整个文本框都能显示出来,如果只有一些像素可见,那么代码就不会进入条件。 - htafoya
1
这个解决方案在横向模式下不起作用——文本字段飞出了视口的顶部。使用iOS 7.1的iPad。 - Andrew
4
为更好支持iOS 8,建议在获取键盘尺寸时使用UIKeyboardFrameEndUserInfoKey而非UIKeyboardFrameBeginUserInfoKey,因为这将捕捉自定义键盘变化以及打开/关闭预测文本等内容。 - Endareth
1
@Egor:你的修复使它运行得更好了 - 但是最后一行必须是相反的:self.scrollView.contentOffset = self.currentSVoffset; - Morten Holmgaard
显示剩余12条评论

253

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 5

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.animate(withDuration: movementDuration, delay: 0, options: [.beginFromCurrentState]) {
        self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
    }
}

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

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

1
为什么不使用 [UIView animateWithDuration: animations:^{ }]; - Andre Cytryn
2
这个工作得很好,尽管 const int movementDistance = -130; // 根据需要进行微调 需要更灵活的改变。 - Hammer
7
在小型实现方案上非常简单。不需要在ScrollViews和模棱两可的自动布局问题上瞎搞。 - James Perih
6
你完全没有使用textField参数。那么为什么要将它作为函数参数呢?此外,在Swift中,你也可以使用三元运算符。这样可以使代码更简洁。 - stk
1
如果视图的背景颜色不是黑色,请确保将窗口的颜色设置为与您的视图相匹配,以便用户无法看到其后面的内容。例如:self.window.backgroundColor = [UIColor whiteColor]; - bvmobileapps
显示剩余7条评论

136

只使用文本字段:

1a)使用Interface Builder:选择所有的文本字段 => 编辑 => 嵌入式滚动视图

1b)手动将文本字段嵌入名为scrollView的UIScrollView中

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

8
textField 已经可见时,它也会将视图向上移动。 - TheTiger
1
需要将 CGPointMake(0, textField.frame.origin.y); 更改为 CGPointMake(0, textField.frame.origin.y + scrollView.contentInset.top); - Jun
即使有 @Egor 的评论,它仍然无法正常工作。就像 "TheTiger" 提到的那样,在文本字段可见之后,它仍然会将视图向上移动。 - rak appdev
XCode 10的更改: "选择所有文本字段=>编辑器=>嵌入式=>滚动视图" - tibalt

121

对于通用解决方案,我采用了以下步骤来实现IQKeyboardManager

enter image description here

步骤1:我在一个单例类中添加了UITextFieldUITextViewUIKeyboard的全局通知。我称之为IQKeyboardManager

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

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

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

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

步骤6:我计算了topMostViewController.view的扰动距离,需要将其恢复到原始位置。

步骤7:我根据扰动距离向下恢复了topMostViewController.view.frame

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

这就是IQKeyboardManager为您做的所有事情,无需编写任何代码!只需要将相关源文件拖放到项目中即可。 IQKeyboardManager还支持设备方向自动UIToolbar管理KeybkeyboardDistanceFromTextField等许多功能。


将 IQKeyBoardManagerSwift 目录添加到我的项目中,但无法工作。无法启用,因为在 AppDelegate 中无法识别它... - user3722523
2
这感觉像是网络钓鱼,实际解决方案没有展示,而是看到了一个商业广告链接到这个人的GitHub账户。 - Brian Bird

102
我已经整理了一个通用的、可插入UIScrollViewUITableView甚至UICollectionView子类,它可以处理在键盘出现时将其中所有文本字段移到键盘之外的问题。
当键盘即将出现时,该子类将找到即将被编辑的子视图,并调整其框架和内容偏移量,以确保该视图可见,并使用与键盘弹出相匹配的动画。当键盘消失时,它会恢复其之前的大小。
它应该适用于基本上任何设置,包括基于UITableView的界面或手动放置视图的界面。
这是它:将文本字段移出键盘之路的通用解决方案

做一些奇怪的事情,比如我的滚动视图全部适合屏幕,所以无法滚动。在打开和关闭键盘后,内容现在变得更大了(看起来好像在页面底部添加了不可见的东西而没有删除),并且可以滚动。 - Almo

94

针对 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方法中执行此操作。


3
SwiftLint 不喜欢 self.view.frame = CGRectOffset(self.view.frame, 0, movement) 这一行,所以我将该行更改为 self.view.frame.offsetInPlace(dx: 0, dy: movement) - levibostian
2
将Swift 4中的self.view.frame = CGRectOffset(self.view.frame, 0, movement)更改为self.view.frame.offsetBy(dx: 0, dy: movement)。 - Asinox
请注意,为了使其正常工作,您必须放置 self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)。 - Joshua Wolff

67

已经有很多答案了,但以上的解决方案都没有所有花哨的定位功能,这些功能是实现“完美”的无错误、向后兼容和无闪烁动画所必需的。(在同时动画帧/边界和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 的回答有很大的改进。但是键盘消失后,视图并没有返回到第一位置。这仍然是一个很棒的作品 :) - Lucien
2
谢谢,这是我在2017年使用的最佳解决方案。请注意,您不需要自己跟踪focusedControl,您可以通过UIApplication.shared.sendAction(...)来确定它。以下是您的答案的Swift 3版本(减去willHide部分),其中实现了sendAction:https://gist.github.com/xaphod/7aab1302004f6e933593a11ad8f5a72d - xaphod
@xaphod 在我的情况下,我需要更加关注控件 - 例如,在输入字段下面的按钮。但是,是的,那段代码现在已经有4年了,可能会从改进中受益。 - Martin Ullrich
这可能是正确的解决方案。键盘通知携带动画数据,文本字段代理不知道动画持续时间,那只能是猜测工作。 - X.Y.

64

Shiun说:"事实证明,我相信UIScrollView会隐式地将当前编辑的UITextField带到可见的窗口中"。 这似乎在iOS 3.1.3上是正确的,但在3.2、4.0或4.1上不正确。 我不得不在iOS >= 3.2中添加一个显式的scrollRectToVisible以使UITextField可见。


UIScrollView并不会自动将编辑过的UITextField滚动到视图中,而是UITextField调用了一个私有方法[UITextField scrollTextFieldToVisibleIfNecessary],该方法在调用[UITextField becomeFirstResponder]时又会调用[UIScrollView scrollRectToVisible]。请参见https://github.com/leopatras/ios_textfields_on_scrollview。如果约束和视图控制器设置正确,则实际上无需显式调用`scrollRectToVisible`(至少从IOS 11开始)。 - Leo
UITextView能否自动处理这种情况,还是我们需要手动处理? - Zaraki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接