【问题标题】:How to re-size UITextView when keyboard shown with iOS 7如何在 iOS 7 中显示键盘时重新调整 UITextView 的大小
【发布时间】:2013-09-28 21:49:16
【问题描述】:

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

这是一个相当标准的 iOS 方法,如以下问题所述:

How to resize UITextView on iOS when a keyboard appears?

但是,在 iOS 7 中,如果用户点击屏幕下半部分的文本视图,当文本视图调整大小时,光标仍会离开屏幕。只有当用户按下回车时,文本视图才会滚动以使光标进入视图。

【问题讨论】:

    标签: objective-c ios7


    【解决方案1】:

    I read the docs which talk about this very topic。我将它翻译成 Swift,它对我来说非常完美。

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

    我在 XCode 6.2 上使用 iOS 8.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)
    }
    

    斯威夫特 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 解决了这个问题,但它没有。您的代码完美运行!
    • 我知道我不应该在评论中这样做,但非常感谢你。我尝试了几个小时将古代(iOS7 和 pre)objective-c 示例移植到 swift 2.1。你拯救了我的夜晚:)
    • 根据苹果的scratch查看最新答案:stackoverflow.com/questions/7169702/…
    【解决方案2】:

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

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

    事实上,在similar question 中,我找到了指向此excellent tutorial 的有关此技术的链接。

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

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

    所以,总结一下:

    1. 使用自动布局
    2. 使用 UIKeyboardWillShowNotification 通知, 不是用于决定何时调整大小的 TextEditField 的事件 观看次数。

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

    【讨论】:

    • 调整大小不是一个好主意。 iOS7 功能不适用于此方法。交互式键盘关闭、键盘半透明等功能。为什么要限制自己的坏想法而不是正确地去做呢?正确的方法是设置content insets。
    • @LeoNatan,您的解决方案看起来有效,但我还没有尝试过。尽管如此,我在我最新的应用程序中使用了调整大小选项,它也适用于 iOS 7,即使使用拆分键盘(我为此使用 UIKeyboardWillChangeFrameNotification,它可以更好地控制 kbd 更改)。然而,我的回复最重要的是人们需要明白,当他们想要对 kbd 的出现做出反应时,他们不能像 ColinE 接受的答案那样使用 textViewDidBeginEditing,因为它不能很好地与外部键盘或具有新的 kbd 大小。
    • 是的,但是交互式键盘关闭不起作用,并且内容不会显示在键盘下。这些是您缺少的重要 iOS7 功能。我建议您为您的应用尝试contentInset 解决方案。
    • 你的意思是按在线kbd的按钮右键关闭它?在我的测试中,这确实发送了正确的消息,并且我的代码调整得很好。你一定是做错了什么。
    • 不,我的意思是从文本滑动到屏幕底部以交互方式关闭键盘。
    【解决方案3】:

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

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


    编辑

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

    - (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,感谢您的回答 - 但是,我已完全按照该答案中的内容剪切和粘贴您的代码,但它仍然无法正常工作。请参阅以下skydrive.live.com/…
    • 刚刚试过了,抱歉,还是不行。看起来@divya 给出的解决方案是正确的。您必须在 textViewDidBeginEditing 方法中更改文本视图框架大小,更改键盘显示通知中的框架在 iOS 7 中不起作用
    • 在计时器上调用 _scrollCaretToVisible 是个坏主意。想象一下,您打算滚动文本以检查某些内容。最好在“- (void)textViewDidChangeSelection:(UITextView *)textView”上调用它。
    • @IgorPchelko 注意有一个检查插入符矩形是否改变。因此,如果您不键入,则计时器回调将返回而不滚动。
    • @Leo Natan,在文本上调用 textViewDidBeginEditing 开始编辑,在文本上调用 textViewDidEndEditing 完成编辑。因此,如果用户在编辑计时器期间尝试滚动文本将不允许以正常方式进行,不是吗?
    【解决方案4】:

    虽然@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 通知之前触发,因此需要对键盘高度进行硬编码。

    【讨论】:

    • 为什么 stackoverflow.com/questions/7169702/… 的标准解决方案在 iOS 7.1 上对我有效?
    • 我再说一遍 - 调整大小不是一个好主意。 iOS7 功能不适用于此方法。交互式键盘关闭、键盘半透明等功能。为什么要限制自己的坏想法而不是正确地去做呢?
    【解决方案5】:

    继续对我有用:

    .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
    

    【讨论】:

    • 朋友,你这样硬编码显示尺寸!
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

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

      我不想按照Apple's documentation 将它嵌入到 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 
      
      }
      

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

      • 它不响应键盘打开/关闭上方的预测文本功能区。 IE。调用键盘时它会考虑它的状态,但是如果您在显示键盘时向上或向下滑动它,则不会调整约束。这是一个需要处理的单独事件。它没有足够的功能让我费心。

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多