当键盘覆盖输入字段但在它们之间留出一些空间时向上移动视图

Posted

技术标签:

【中文标题】当键盘覆盖输入字段但在它们之间留出一些空间时向上移动视图【英文标题】:Move a view up when the keyboard covers an input field but with leaving some space between them 【发布时间】:2016-03-26 14:19:38 【问题描述】:

说明

当其中一个在编辑期间被键盘覆盖时,我想向上滚动一些 UITextField。这里有很多关于 SO 的答案,有很多风格:移动视图(通过改变它的框架),修改约束,使用 UIScrollView 和 UITableView,或者使用 UIScrollView 和修改 contentInset。

我决定使用最后一个。这个也是described by Apple,有一个Swift version on SO,还有一个described on this blog,包括一个sample project on the GitHub。

部分代码

override func viewDidLoad() 
    super.viewDidLoad()
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)


func textFieldShouldReturn(textField: UITextField) -> Bool 
    textField.resignFirstResponder()
    return true


func textFieldDidEndEditing(textField: UITextField) 
    self.activeField = nil


func textFieldDidBeginEditing(textField: UITextField) 
    self.activeField = textField


func keyboardWillShow(notification: NSNotification) 
    if let activeField = self.activeField, keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() 
        let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
        self.scrollView.contentInset = contentInsets
        self.scrollView.scrollIndicatorInsets = contentInsets
        var aRect = self.view.frame
        aRect.size.height -= keyboardSize.size.height
        if (!CGRectContainsPoint(aRect, activeField.frame.origin)) 
            self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
        
    


func keyboardWillBeHidden(notification: NSNotification) 
    let contentInsets = UIEdgeInsetsZero
    self.scrollView.contentInset = contentInsets
    self.scrollView.scrollIndicatorInsets = contentInsets

问题

我只想要一个简单的修改 - 在键盘和已编辑字段之间留出更多空间。因为开箱即用,它看起来像这样:

我在scrollRectToVisible 调用中修改了CGRect,但它没有任何改变。更重要的是,即使用scrollRectToVisible 注释掉这行也没有任何效果——一切都和以前一样(包括向上滚动内容)。在 ios 9.2 上检查

如果需要,复制很简单 - 只需使用上面的 GitHub 链接下载工作代码并注释掉 scrollRectToVisible 行。

经过测试的解决方法

我尝试了解决方法,但不喜欢最终效果:

增加 contentInset - 用户可以向上滚动超过 contentSize 将UIKeyboardDidShowNotification 替换为UIKeyboardWillShowNotification,添加另一个UIKeyboardDidShowNotification 观察者,里面只有scrollRectToVisible - 可以,但是有两个向上滚动的动画,看起来不太好。

问题

为什么更改contentInset(没有scrollRectToVisible 调用)会滚动scrollView 中的内容?我没有在任何文档中看到有关此类行为的信息 更重要的是 - 怎么做,再向上滚动一点,在编辑的文本字段和键盘之间留出一些空间?

我错过了什么?有什么简单的方法可以解决吗?或者使用scrollView.contentSize 而不是contentInset 会更好?

【问题讨论】:

【参考方案1】:

我还没有找到为什么scrollRectToVisible 在上述情况下不起作用,但是我找到了其他可行的解决方案。在最底部提到的Apple文章Managing the Keyboard中有一个提示

还有其他方法可以在滚动视图中滚动编辑区域 在一个模糊的键盘上方。而不是改变底部的内容 滚动视图的插图,您可以扩展内容的高度 按键盘高度查看,然后滚动编辑的文本 对象进入视野。

下面的解决方案完全基于扩展内容视图的高度 (scrollView.contentSize)。它考虑了键盘隐藏时的方向变化和向后滚动。按要求工作 - 在活动字段和键盘之间有一些空间,请参见下图

工作代码

我已将完整的工作代码放在 GitHub 上:ScrollViewOnKeyboardShow

var animateContenetView = true
var originalContentOffset: CGPoint?
var isKeyboardVisible = false

let offset : CGFloat = 18

override func viewDidLoad() 
    super.viewDidLoad()

    for case let textField as UITextField in contentView.subviews 
        textField.delegate = self
    

    let notificationCenter = NSNotificationCenter.defaultCenter()
    notificationCenter.addObserver(self, selector: #selector(ViewController.keyboardWillBeShown(_:)), name: UIKeyboardWillShowNotification, object: nil)
    notificationCenter.addObserver(self, selector: #selector(ViewController.keyboardWillBeHidden(_:)), name: UIKeyboardWillHideNotification, object: nil)



func textFieldShouldReturn(textField: UITextField) -> Bool 
    textField.resignFirstResponder()
    return true



func textFieldDidEndEditing(textField: UITextField) 
    self.activeField = nil



func textFieldDidBeginEditing(textField: UITextField) 
    self.activeField = textField



func keyboardWillBeShown(notification: NSNotification) 
    originalContentOffset = scrollView.contentOffset

    if let activeField = self.activeField, keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() 
        var visibleRect = self.scrollView.bounds
        visibleRect.size.height -= keyboardSize.size.height

        //that's to avoid enlarging contentSize multiple times in case of many UITextFields,
        //when user changes an edited text field
        if isKeyboardVisible == false 
            scrollView.contentSize.height += keyboardSize.height
        

        //scroll only if the keyboard would cover a bottom edge of an
        //active field (including the given offset)
        let activeFieldBottomY = activeField.frame.origin.y + activeField.frame.size.height + offset
        let activeFieldBottomPoint = CGPoint(x: activeField.frame.origin.x, y: activeFieldBottomY)
        if (!CGRectContainsPoint(visibleRect, activeFieldBottomPoint)) 
            var scrollToPointY = activeFieldBottomY - (self.scrollView.bounds.height - keyboardSize.size.height)
            scrollToPointY = min(scrollToPointY, scrollView.contentSize.height - scrollView.frame.size.height)

            scrollView.setContentOffset(CGPoint(x: 0, y: scrollToPointY), animated: animateContenetView)
        
    

    isKeyboardVisible = true



func keyboardWillBeHidden(notification: NSNotification) 
    scrollView.contentSize.height = contentView.frame.size.height
    if var contentOffset = originalContentOffset 
        contentOffset.y = min(contentOffset.y, scrollView.contentSize.height - scrollView.frame.size.height)
        scrollView.setContentOffset(contentOffset, animated: animateContenetView)
    

    isKeyboardVisible = false



override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) 
    coordinator.animateAlongsideTransition(nil)  (_) -> Void in
        self.scrollView.contentSize.height = self.contentView.frame.height
    



deinit 
    let notificationCenter = NSNotificationCenter.defaultCenter()
    notificationCenter.removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
    notificationCenter.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)

【讨论】:

这适用于文本字段。进行必要的更改后,文本视图是另一回事。

以上是关于当键盘覆盖输入字段但在它们之间留出一些空间时向上移动视图的主要内容,如果未能解决你的问题,请参考以下文章

当键盘出现Swift时,使用文本字段和按钮向上移动视图

出现键盘时移动文本字段会覆盖(Swift)中的顶部文本字段

在输入之间保持 iPad 键盘向上

键盘存在时 UIViewController 向上移动

如何修改我的四个象限以在它们之间留出一些“喘息空间”(HTML/CSS)?

键盘出现多次时,滚动视图不会向上移动