如何在iOS 7中显示键盘时调整UITextView的大小

14

我有一个包含全屏UITextView的视图控制器。当键盘出现时,我希望调整文本视图的大小,以便不会被键盘遮挡。

这是iOS中相当标准的做法,如在以下问题中所述:

如何在iOS上调整UITextView大小以适应键盘出现?

然而,在iOS 7上,如果用户点击屏幕下半部分的文本视图,当文本视图重新调整大小时,光标仍然偏离屏幕。只有当用户按回车键时,文本视图才会滚动以使光标可见。

8个回答

26

我阅读了关于这个话题的文档。我将它翻译成Swift并且它对我非常有效。

这适用于像iMessage那样的全屏UITextView。

我正在使用iOS 8.2和XCode 6.2上的Swift,这是我的代码。只需从您的viewDidLoad或其他初始化方法中调用此setupKeyboardNotifications即可。

func setupKeyboardNotifications() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as NSValue
    let kbSize = infoNSValue.CGRectValue().size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsetsZero
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

如果你在旋转时发现光标位置不正确,请检查方向变化并滚动到正确的位置。

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
    scrollToCaretInTextView(codeTextView, animated: true)
}

func scrollToCaretInTextView(textView:UITextView, animated:Bool) {
    var rect = textView.caretRectForPosition(textView.selectedTextRange?.end)
    rect.size.height += textView.textContainerInset.bottom
    textView.scrollRectToVisible(rect, animated: animated)
}

Swift 3:

func configureKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(aNotification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(aNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
    let kbSize = infoNSValue.cgRectValue.size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsets.zero
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}

Swift 4 & 5:

func setupKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_ :)), name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}



@objc func keyboardWillShow(_ notification:NSNotification) {
    let d = notification.userInfo!
    var r = (d[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    r = self.textView.convert(r, from:nil)
    self.textView.contentInset.bottom = r.size.height
    self.textView.verticalScrollIndicatorInsets.bottom = r.size.height

}

@objc func keyboardWillHide(_ notification:NSNotification) {
    let contentInsets = UIEdgeInsets.zero
    self.textView.contentInset = contentInsets
    self.textView.verticalScrollIndicatorInsets = contentInsets
}

太棒了!非常感谢!我已经寻找了几个小时的解决方案,但都没有成功。每个人都说iOS 8修复了这个问题,但实际上并没有。你的代码完美地解决了这个问题! - Jacob
我知道我不应该在评论中这样做,但是非常感谢你..我花了几个小时尝试将古老的(iOS7及以前)Objective-C示例移植到Swift 2.1。你救了我的夜晚 :) - Shirkrin
请查看基于苹果Scratch的最新答案:https://dev59.com/BWw05IYBdhLWcg3wiyjg#35385854 - Dmitry

4
使用自动布局,如果你理解了自动布局,处理以下问题就会变得更加容易:
不需要尝试识别和调整受影响的视图,只需为所有视图内容创建一个父框架。然后,如果键盘出现,您可以调整框架大小,如果您已经正确设置了约束,则视图将重新排列其所有子视图。这样做无需大量难以阅读的代码。
实际上,在类似的问题中,我找到了一个关于这种技术的优秀教程的链接。
此外,这里的其他示例使用textViewDidBeginEditing而不是UIKeyboardWillShowNotification存在一个大问题:
如果用户连接了外部蓝牙键盘,则即使没有屏幕键盘出现,控件仍会被推上去。这并不好。
因此,总结一下:
  1. 使用自动布局
  2. 使用UIKeyboardWillShowNotification通知,而不是TextEditField的事件来决定何时调整视图大小。

或者,查看LeoNatan的回复。那可能是一个更清晰、更简单的解决方案(我还没有尝试过)。


调整大小不是一个好主意。iOS7的功能将无法使用此方法。例如交互式键盘消失、键盘半透明等功能。为什么要限制自己的思维,而不是正确地做呢?正确的方法是设置内容插图。 - Léo Natan
@LeoNatan,你的解决方案看起来是有效的,虽然我还没有尝试过。不过,我在我的最新应用程序中使用了调整大小选项,在iOS 7上也能很好地工作,即使使用分屏键盘(我使用UIKeyboardWillChangeFrameNotification来实现这一点,这可以更好地控制键盘的变化)。然而,我回复最重要的是人们需要明白,当他们想要对键盘出现做出反应时,他们不能像ColinE的被接受答案那样使用textViewDidBeginEditing,因为它在外部键盘或新键盘大小上无法正常工作。 - Thomas Tempelmann
是的,但交互式键盘消失不起作用,内容也不会显示在键盘下面。这些是您缺少的重要iOS7功能。我建议您尝试使用contentInset解决方案来改进您的应用程序。 - Léo Natan
你的意思是按下在线键盘的右侧按钮来关闭它吗?在我的测试中,这确实发送了正确的消息,我的代码也可以很好地调整。你一定是做错了什么。 - Thomas Tempelmann
不,我的意思是通过从文本向屏幕底部滑动来交互式地关闭键盘。 - Léo Natan

3
不要调整文本视图的大小。相反,将contentInsetscrollIndicatorInsets底部设置为键盘的高度。
请查看我在这里的答案:https://dev59.com/1WMl5IYBdhLWcg3wZWSD#18585788
编辑
我对您的示例项目进行了以下更改:
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    _caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}

- (void)_scrollCaretToVisible
{
    //This is where the cursor is at.
    CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    if(CGRectEqualToRect(caretRect, _oldRect))
        return;

    _oldRect = caretRect;

    //This is the visible rect of the textview.
    CGRect visibleRect = self.textView.bounds;
    visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
    visibleRect.origin.y = self.textView.contentOffset.y;

    //We will scroll only if the caret falls outside of the visible rect.
    if(!CGRectContainsRect(visibleRect, caretRect))
    {
        CGPoint newOffset = self.textView.contentOffset;

        newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);

        [self.textView setContentOffset:newOffset animated:NO];
    }
}

优化了原有光标位置设置和禁用动画的设置。现在看起来已经运行良好。


Leo,感谢您的回答 - 但是,我已经完全按照您在那个答案中提供的代码进行了剪切和粘贴,但它仍然无法正常工作。请参见以下链接:https://skydrive.live.com/redir?resid=B4EA9717AAB33DAF!13475&authkey=!APyu4YbcgmHhUnY - ColinE
刚试了一下,很抱歉它还是不起作用。看起来@divya提供的解决方案是正确的。你必须在textViewDidBeginEditing方法中改变文本视图框架大小,在键盘显示通知中改变框架在iOS 7中不起作用。 - ColinE
在计时器上调用_scrollCaretToVisible是个不好的主意。想象一下,你打算滚动文本以检查某些内容。最好在“- (void)textViewDidChangeSelection:(UITextView *)textView”上调用它。 - Igor Pchelko
注意,这里检查了光标区域是否改变。因此,如果你没有打字,定时器回调将在不滚动的情况下返回。 - Léo Natan
@Leo Natan,“textViewDidBeginEditing”在文本开始编辑时被调用,“textViewDidEndEditing”在文本编辑完成时被调用。所以,如果用户在编辑期间尝试滚动文本,计时器将不允许以正常方式执行,是吗? - Igor Pchelko
不,滚动将是被允许的。每个tick时,它会检查游标是否发生了变化。只有在发生变化时,文本视图才会滚动。 - Léo Natan

3
虽然 @Divya 给出的回答让我找到了正确的解决方案(所以我奖励了悬赏),但它并不是非常清晰明了的答案!以下是详细过程:
确保文本视图不被屏幕键盘隐藏的标准方法是在键盘显示时更新其框架,如此问题中所述: How to resize UITextView on iOS when a keyboard appears? 然而,在 iOS 7 中,如果您在处理 UIKeyboardWillShowNotification 通知的处理程序内更改文本视图框架,则光标将始终保持在屏幕外,正如这个问题所描述的那样。
解决此问题的方法是在 textViewDidBeginEditing 委托方法响应中更改文本视图框架。
@implementation ViewController {
    CGSize _keyboardSize;
    UITextView* textView;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    textView = [[UITextView alloc] initWithFrame:CGRectInset(self.view.bounds, 20.0, 20.0)];    textView.delegate = self;
    textView.returnKeyType = UIReturnKeyDone;
    textView.backgroundColor = [UIColor greenColor];
    textView.textColor = [UIColor blackColor];
    [self.view addSubview:textView];


    NSMutableString *textString = [NSMutableString new];
    for (int i=0; i<100; i++) {
        [textString appendString:@"cheese\rpizza\rchips\r"];
    }
    textView.text = textString;

}

- (void)textViewDidBeginEditing:(UITextView *)textView1 {
    CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
    textViewFrame.size.height -= 216;
    textView.frame = textViewFrame;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
    textView.frame = textViewFrame;
    [textView endEditing:YES];
    [super touchesBegan:touches withEvent:event];
}

@end

注意: 不幸的是,textViewDidBeginEdtingUIKeyboardWillShowNotification 通知之前触发,因此需要硬编码键盘高度。


为什么https://dev59.com/BWw05IYBdhLWcg3wiyjg的标准解决方案在iOS 7.1上适用于我? - Dmitry
我再说一遍 - 调整大小不是一个好主意。iOS7的功能将无法使用此方法。例如交互式键盘消失、键盘半透明等功能。为什么要限制自己的思维,而不是正确地做呢? - Léo Natan

2
以下是对我有效的工作方式:

.h文件

@interface ViewController : UIViewController <UITextViewDelegate> {

    UITextView *textView ;

}

@property(nonatomic,strong)IBOutlet UITextView *textView;

@end

.m文件

@implementation ViewController
@synthesize textView;
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);
    //UITextView *textView = [[UITextView alloc] initWithFrame:textViewFrame];
    textView.frame = textViewFrame;
    textView.delegate = self;
    textView.returnKeyType = UIReturnKeyDone;
    textView.backgroundColor = [UIColor greenColor];
    textView.textColor = [UIColor blackColor];
    [self.view addSubview:textView];

}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
    NSLog(@"textViewShouldBeginEditing:");
    return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView1 {
    NSLog(@"textViewDidBeginEditing:");
   CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 224.0f);

    textView1.frame = textViewFrame;

}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
    NSLog(@"textViewShouldEndEditing:");
       return YES;
}
- (void)textViewDidEndEditing:(UITextView *)textView{
    NSLog(@"textViewDidEndEditing:");
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
       return YES;
}

- (void)textViewDidChange:(UITextView *)textView{
    NSLog(@"textViewDidChange:");
}

- (void)textViewDidChangeSelection:(UITextView *)textView{
    NSLog(@"textViewDidChangeSelection:");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"touchesBegan:withEvent:");
    CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);

    textView.frame = textViewFrame;
    [self.view endEditing:YES];
    [super touchesBegan:touches withEvent:event];
}
@end

4
朋友,你这样是在硬编码显示尺寸! - Pedro Borges

0

@Johnston 找到了一个好的解决方案。这里有一个变体,使用 UIKeyboardWillChangeFrameNotification 来正确处理键盘大小的更改(即显示/隐藏 QuickType 栏)。它还可以正确处理文本视图嵌入导航控制器的情况(即 contentInset 不为零)。它也是用 Swift 2 编写的。

override func viewDidLoad() {
    :

    NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil) { (notification) -> Void in
        guard let userInfo = notification.userInfo,
            let keyboardFrameEndValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
            else { return }

        let windowCoordinatesKeyboardFrameEnd = keyboardFrameEndValue.CGRectValue() // window coordinates
        let keyboardFrameEnd = self.view.convertRect(windowCoordinatesKeyboardFrameEnd, fromView: nil) // view coordinates

        var inset = self.textView.contentInset
        inset.bottom = CGRectGetMaxY(self.textView.frame) - CGRectGetMinY(keyboardFrameEnd) // bottom inset is the bottom of textView minus top of keyboard
        self.textView.contentInset = inset
        self.textView.scrollIndicatorInsets = inset
    }
}

0

我已经完成了它,而且它完全可行。

  #define k_KEYBOARD_OFFSET 95.0

-(void)keyboardWillAppear {
    // Move current view up / down with Animation
    if (self.view.frame.origin.y >= 0)
    {
        [self moveViewUp:NO];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self moveViewUp:YES];
    }
}

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

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

//Custom method to move the view up/down whenever the keyboard is appeared / disappeared
-(void)moveViewUp:(BOOL)bMovedUp
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.4]; // to slide the view up

    CGRect rect = self.view.frame;
    if (bMovedUp) {
        // 1. move the origin of view up so that the text field will come above the keyboard
        rect.origin.y -= k_KEYBOARD_OFFSET;

        // 2. increase the height of the view to cover up the area behind the keyboard
        rect.size.height += k_KEYBOARD_OFFSET;
    } else {
        // revert to normal state of the view.
        rect.origin.y += k_KEYBOARD_OFFSET;
        rect.size.height -= k_KEYBOARD_OFFSET;
    }

    self.view.frame = rect;

    [UIView commitAnimations];
}

- (void)viewWillAppear:(BOOL)animated
{
    // register keyboard notifications to appear / disappear the keyboard
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillAppear)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];

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

- (void)viewWillDisappear:(BOOL)animated
{
    // unregister for keyboard notifications while moving to the other screen.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillShowNotification
                                                  object:nil];

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

0

这是我的解决方案,使用Swift 1.2在Xcode 6.4上于2015年7月针对iOS 7.1进行开发 - 结合了几种方法。借用了 Johnston的键盘处理Swift代码。虽然有点hack,但它很简单并且有效。

我在一个单独的视图中放置了一个普通的UITextView。

我不想像 Apple的文档中那样将其嵌入UIScrollView中。我只想在软件键盘出现时重新调整UITextView的大小,并在键盘消失时恢复原始大小。

以下是基本步骤:

  1. 设置键盘通知
  2. 在“Interface Builder”中设置布局约束(在我的情况下是将TextView设置到底部边缘)
  3. 在相关代码文件中创建IBOutlet以便可以通过编程方式调整它
  4. 使用键盘通知拦截事件并获取键盘大小
  5. 使用键盘大小以编程方式调整约束IBOutlet以重新调整TextView的大小。
  6. 当键盘消失时,将所有内容放回原处。

接下来是代码部分。

我已经通过界面构建器中的常规拖放设置了代码文件顶部的约束输出口:@IBOutlet weak var myUITextViewBottomConstraint: NSLayoutConstraint!

我还设置了一个全局变量,可以在键盘出现之前备份状态:var myUITextViewBottomConstraintBackup: CGFloat = 0

实现键盘通知,在viewDidLoad或任何其他启动/设置部分中调用此函数:

func setupKeyboardNotifications() {

    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)

    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)

    }

当键盘显示/隐藏时,这两个函数将自动调用:
func keyboardWasShown(aNotification:NSNotification) {

    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
    let kbSize = infoNSValue.CGRectValue().size

    let newHeight = kbSize.height

    //backup old constraint size
    myUITextViewBottomConstraintOld = myUITextViewBottomConstraint.constant 

    // I subtract 50 because otherwise it leaves a gap between keyboard and text view. I'm sure this could be improved on.
    myUITextViewBottomConstraint.constant = newHeight - 50 

func keyboardWillBeHidden(aNotification:NSNotification) {
    //restore to whatever AutoLayout set it before you messed with it
    myUITextViewBottomConstraint.constant = myUITextViewBottomConstraintOld 

}

代码可以运行,但有一个小问题:

  • 它不响应键盘上方的预测文本条打开/关闭。也就是说,当调用键盘时,它会考虑其状态,但如果您在键盘显示时将其向上或向下滑动,则约束不会被调整。这是需要处理的单独事件。对我来说,这不足以影响功能。

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