如何在模态呈现的表单中为 UIScrollView 计算正确的键盘 contentInset sheet UIViewController

How to calculate proper keyboard contentInset for UIScrollView inside of a modally presented form sheet UIViewController

我遇到一个问题,依靠 convertRect 正确报告用于计算 contentInset 的 y 位置在 iOS 12 上不起作用。这种方法曾经有效在早期 iOS 版本中:

@objc func keyboardVisibilityChanged(notification: Notification) {
    guard let userInfo = notification.userInfo else {
        assertionFailure()
        return
    }

    let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    let keyboardViewEndFrame = scrollView.convert(keyboardScreenEndFrame, from: view.window!)

    if notification.name == UIResponder.keyboardWillHideNotification {
        scrollView.contentInset = .zero
        scrollView.scrollIndicatorInsets = .zero
    } else {
        let insets = UIEdgeInsets(top: 0, left: 0, bottom: (keyboardViewEndFrame.origin.y - keyboardViewEndFrame.size.height) , right: 0)
        scrollView.contentInset = insets
        scrollView.scrollIndicatorInsets = insets
    }
}

但是,此代码虽然实现了极其接近的视觉效果,但并不准确,并且还会在 iPhone 上中断,其中模态全屏显示。

Apple states 在他们的文档中:

Note: The rectangle contained in the UIKeyboardFrameBeginUserInfoKey and UIKeyboardFrameEndUserInfoKey properties of the userInfo dictionary should be used only for the size information it contains. Do not use the origin of the rectangle (which is always {0.0, 0.0}) in rectangle-intersection operations. Because the keyboard is animated into position, the actual bounding rectangle of the keyboard changes over time.

所以我想出了以下似乎在 iOS 13、12 和 11 上运行良好的解决方案,包括安全区域、模式表单和硬件键盘):

// MARK: - Keyboard Notifications
@objc func keyboardVisibilityChanged(notification: Notification) {       
    if notification.name == UIResponder.keyboardWillHideNotification {
        scrollView.contentInset = .zero
        scrollView.scrollIndicatorInsets = .zero
    } else {
        guard let userInfo = notification.userInfo,
            let value = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
            let window = view.window else {
                assertionFailure()
                return
        }

        let keyboardEndFrameInWindowCoordinates = value.cgRectValue
        let viewFrameInWindowCoordinates = window.convert(scrollView.frame,
                                                          from: scrollView.superview)

        let contentInsetBottom: CGFloat

        // If the keyboard is below us, no need to do anything.
        // This can happen when a hardware keyboard is attached to a modal form sheet on iPad
        if keyboardEndFrameInWindowCoordinates.origin.y >= viewFrameInWindowCoordinates.maxY {
            contentInsetBottom = 0
        } else {
            let bottomEdgeOfViewInWindowBottomCoordinates = window.frame.maxY - viewFrameInWindowCoordinates.maxY
            contentInsetBottom = keyboardEndFrameInWindowCoordinates.height - bottomEdgeOfViewInWindowBottomCoordinates - view.safeAreaInsets.bottom
        }

        let insets = UIEdgeInsets(top: 0,
                             left: 0,
                             bottom: contentInsetBottom,
                             right: 0)

        scrollView.scrollIndicatorInsets = insets
    }
}