objective-c 如何在 iOS 7 中显示键盘时重新调整 UITextView 的大小

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18968735/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 21:01:51  来源:igfitidea点击:

How to re-size UITextView when keyboard shown with iOS 7

objective-cios7

提问by ColinE

I have a view controller which contains a full-screen UITextView. When the keyboard is shown I would like to resize the text view so that it is not hidden under the keyboard.

我有一个包含全屏UITextView. 当显示键盘时,我想调整文本视图的大小,使其不会隐藏在键盘下。

This is a fairly standard approach with iOS, as described in this question:

这是 iOS 的一种相当标准的方法,如本问题所述:

How to resize UITextView on iOS when a keyboard appears?

出现键盘时如何在 iOS 上调整 UITextView 的大小?

However, with iOS 7, if the user taps on the text view in the bottom half of the screen, when the text view resizes, the cursor remains offscreen. The text view only scrolls to bring the cursor into view if when the user hits enter.

然而,在 iOS 7 中,如果用户点击屏幕下半部分的文本视图,当文本视图调整大小时,光标会保持在屏幕外。如果用户按下回车键,文本视图只会滚动以将光标置于视图中。

采纳答案by ColinE

Whilst the answer given by @Divya lead me to the correct solution (so I awarded the bounty), it is not a terribly clear answer! Here it is in detail:

虽然@Divya 给出的答案让我找到了正确的解决方案(所以我授予了赏金),但这并不是一个非常明确的答案!这里有详细介绍:

The standard approach to ensuring that a text view is not hidden by the on-screen keyboard is to update its frame when the keyboard is shown, as detailed in this question:

确保文本视图不被屏幕键盘隐藏的标准方法是在显示键盘时更新其框架,如本问题所述:

How to resize UITextView on iOS when a keyboard appears?

出现键盘时如何在 iOS 上调整 UITextView 的大小?

However, with iOS 7, if you change the text view frame within your handler for the UIKeyboardWillShowNotificationnotification, the cursor will remain off screen as described in this question.

但是,在 iOS 7 中,如果您更改UIKeyboardWillShowNotification通知处理程序中的文本视图框架,光标将保持在屏幕外,如本问题所述。

The fix for this issue is to change the text view frame in response to the textViewDidBeginEditingdelegate method instead:

此问题的解决方法是更改​​文本视图框架以响应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

NOTE:unfortunately textViewDidBeginEdtingfires before the UIKeyboardWillShowNotificationnotification, hence the need to hard-code the keyboard height.

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

回答by Johnston

I read the docs which talk about this very topic. I translated it into Swift and it worked absolutely beautifully for me.

我阅读了讨论这个主题的文档。我将它翻译成 Swift,它对我来说非常棒。

This is used for a full page UITextView like iMessage.

这用于像 iMessage 这样的整页 UITextView。

I am using iOS 8.2 and Swift on XCode 6.2 and here's my code. Just call this setupKeyboardNotificationsfrom your viewDidLoador other initialization method.

我在 XCode 6.2 上使用 iOS 8.2 和 Swift,这是我的代码。只需setupKeyboardNotifications从您的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 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
}

Also if you are having issues with the caret being in the right place when rotated check for the orientation change and scroll to the right position.

此外,如果您在旋转时插入符号位于正确位置时遇到问题,请检查方向更改并滚动到正确位置。

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:

斯威夫特 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:

斯威夫特 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
}

回答by Thomas Tempelmann

With Auto Layout, it's much easier (provided you understand Auto Layout) to handle:

使用自动布局,处理起来要容易得多(前提是您了解自动布局):

Instead of trying to identify and resize the affected views, you simply create a parent frame for all your view's contents. Then, if the kbd appears, you resize the frame, and if you've set up the constraints properly, the view will re-arrange all its child views nicely. No need to fiddle with lots of hard-to-read code for this.

无需尝试识别受影响的视图并调整其大小,您只需为所有视图的内容创建一个父框架。然后,如果 kbd 出现,您调整框架的大小,并且如果您正确设置了约束,则视图将很好地重新排列其所有子视图。无需为此摆弄大量难以阅读的代码。

In fact, in a similar questionI found a link to this excellent tutorialabout this technique.

事实上,在一个类似的问题中,我找到了一个关于这个技术的优秀教程的链接。

Also, the other examples here that do use textViewDidBeginEditing instead of the UIKeyboardWillShowNotification have one big issue:

此外,此处使用 textViewDidBeginEditing 而不是 UIKeyboardWillShowNotification 的其他示例有一个大问题:

If the user has an external bluetooth keyboard attached then the control would still get pushed up even though no on-screen keyboard appears. That's not good.

如果用户连接了外部蓝牙键盘,那么即使没有出现屏幕键盘,控件仍然会被向上推。这不好。

So, to summarize:

所以,总结一下:

  1. Use Auto Layout
  2. Use the UIKeyboardWillShowNotification notification, not the TextEditField's events for deciding when to resize your views.
  1. 使用自动布局
  2. 使用 UIKeyboardWillShowNotification 通知,而不是 TextEditField 的事件来决定何时调整视图大小。

Alternatively, check out LeoNatan's reply. That might even be a cleaner and simpler solution (I've not tried myself yet).

或者,查看 LeoNatan 的回复。这甚至可能是一个更干净、更简单的解决方案(我自己还没有尝试过)。

回答by Leo Natan

Do not resize the text view. Instead, set the contentInsetand scrollIndicatorInsetsbottom to the keyboard height.

不要调整文本视图的大小。相反,将contentInsetscrollIndicatorInsets底部设置为键盘高度。

See my answer here: https://stackoverflow.com/a/18585788/983912

在此处查看我的答案:https: //stackoverflow.com/a/18585788/983912



Edit

编辑

I made the following changes to your sample project:

我对您的示例项目进行了以下更改:

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

Removed setting old caret position at first, as well as disabled animation. Now seems to work well.

删除了最初设置旧的插入符号位置,以及禁用动画。现在似乎运作良好。

回答by Rigel Networks

Following on is working for me :

以下是对我来说有效:

.h file

.h 文件

@interface ViewController : UIViewController <UITextViewDelegate> {

    UITextView *textView ;

}

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

@end

.m file

.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

回答by Patel Jigar

i had done it and its work completely.

我已经完成了它和它的工作。

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

回答by barko

This is my solution, July 2015 using Swift 1.2 on Xcode 6.4 targeting iOS 7.1 - a combination of several approaches. Borrowed Johnston'skeyboard handing Swift code. Its a bit of a hack, but its simple and it works.

这是我的解决方案,2015 年 7 月在针对 iOS 7.1 的 Xcode 6.4 上使用 Swift 1.2 - 几种方法的组合。借用Johnston 的键盘来处理 Swift 代码。它有点黑客,但它简单并且有效。

I have a vanilla UITextView inside a single View.

我在单个视图中有一个普通的 UITextView。

I did not want to embed it inside a UIScrollView as per Apple's documentation. I just wanted the UITextView re-sized when software keyboard appeared, and resized to original when keyboard was dismissed.

我不想按照Apple 的文档将它嵌入到 UIScrollView 中。我只是希望 UITextView 在出现软件键盘时重新调整大小,并在键盘关闭时调整为原始大小。

These are the basic steps:

这些是基本步骤:

  1. Set up keyboard notifications
  2. Set up layout constraint in "Interface Builder" (TextView to bottom edge in my case)
  3. Create an IBOutlet for this constraint in the relevant code file so you can adjust it programmatically
  4. Use keyboard notifications to intercept events and get keyboard size
  5. Programmatically adjust constraint IBOutlet using keyboard size to re-size TextView.
  6. Put everything back when keyboard is dismissed.
  1. 设置键盘通知
  2. 在“Interface Builder”中设置布局约束(在我的情况下,TextView 到底部边缘)
  3. 在相关代码文件中为此约束创建一个 IBOutlet,以便您可以以编程方式对其进行调整
  4. 使用键盘通知拦截事件并获取键盘大小
  5. 使用键盘大小以编程方式调整约束 IBOutlet 以重新调整 TextView 的大小。
  6. 关闭键盘时将所有内容放回原处。

So, onto the code.

所以,上代码。

I've set up constraint outlet at the top of the code file via the usual drag-drop in interface builder: @IBOutlet weak var myUITextViewBottomConstraint: NSLayoutConstraint!

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

I also set up a global variable where I can back up the state of affairs before the keyboard come up: var myUITextViewBottomConstraintBackup: CGFloat = 0

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

Implement keyboard notifications, call this function in viewDidLoad or any other startup/setup section:

实现键盘通知,在 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)

    }

Then these two functions will be called automatically when keyboard is shown/dismissed:

然后在显示/关闭键盘时将自动调用这两个函数:

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 

}

The code works, with a minor issue:

该代码有效,但有一个小问题:

  • It's not responsive to the predictive text ribbon above the keyboard opening/closing. I.e. it will take the state of it into account when the keyboard is called up, but if you were to slide it up or down while keyboard is shown the constraint will not be adjusted. It is a separate event that needs to be handled. Its not enough of a functionality hit for me to bother with.
  • 它不响应键盘打开/关闭上方的预测文本功能区。即它会在键盘被调用时考虑它的状态,但是如果你在显示键盘时向上或向下滑动它,则不会调整约束。这是一个需要处理的单独事件。对我来说,功能命中还不够。

回答by jrc

@Johnston found a good solution. Here's a variation using UIKeyboardWillChangeFrameNotificationwhich correctly accounts for keyboard size changes (i.e. showing/hiding the QuickType bar). It also correctly handles the case where the text view is embedded in a navigation controller (i.e. where the contentInsetisn't otherwise zero). It's also written in Swift 2.

@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
    }
}