ios - 当键盘覆盖输入字段时向上移动 View ,但在它们之间留有一些空间

标签 ios swift uiscrollview uikit uikeyboard

描述

我有一些 UITextField,当其中之一在编辑过程中被键盘覆盖时,我想向上滚动。这里有大量关于 SO 的答案,有多种风格:移动 View (通过更改其框架)、修改约束、使用 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
}

问题

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

screenshot

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

如果需要,复制是微不足道的 - 只需使用上面的 GitHub 链接下载工作代码并注释掉scrollRectToVisible 行即可。

经过测试的解决方法

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

  • 增加 contentInset - 用户可以向上滚动超过 contentSize 的内容
  • UIKeyboardDidShowNotification 替换为 UIKeyboardWillShowNotification,添加另一个 UIKeyboardDidShowNotification 观察者,其中仅包含 scrollRectToVisible - 它可以工作,但随后有两个向上滚动的动画,看起来不太好。

问题

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

我错过了什么?有一些简单的方法可以修复它吗?或者也许使用 scrollView.contentSize 更好,而不是 contentInset

最佳答案

我还没有找到为什么 scrollRectToVisible 在上述场景中不起作用,但是我找到了其他有效的解决方案。在提到的苹果文章中Managing the Keyboard最底部有提示

There are other ways you can scroll the edited area in a scroll view above an obscuring keyboard. Instead of altering the bottom content inset of the scroll view, you can extend the height of the content view by the height of the keyboard and then scroll the edited text object into view.

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

screenshot

工作代码

我已将完整的工作代码放在 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)
}

关于ios - 当键盘覆盖输入字段时向上移动 View ,但在它们之间留有一些空间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36236352/

相关文章:

ios - 表格 View 中隐藏的自定义范围 slider

ios - 如何在完整尺寸的 UIView 上添加大背景图片?

ios - 可以为代码部分添加类型别名吗?

string - SWIFT:大写字符串的性能

ios - 无法在 iOS 13 上创建小型大写字母选择器

ios - 当键盘存在时如何使 UITextField 向上移动 - 开始编辑?

iphone - 在 IOS 上的单个 ScrollView 中对两个 ImageView 进行双指缩放

ios - WKWebView 中的 EXC_BAD_ACCESS 和/或 EXC_BREAKPOINT - 可能的更改以快速修复

ios - 核心数据崩溃 NSInternalInconsistencyException 'statement is still active'

ios - UIScrollView 将元素定位在屏幕底部,而不是 ScrollView 底部