是否可以在滚动视图中使用 pageviewcontroller?

Is it possible to use a pageviewcontroller inside a scrollview?

我有一个 pageviewcontroller,它可以水平滚动,显示问题和答案。

pageviewcontroller 的 contentView 填充了一个 scrollview,里面是一些堆栈视图,其中一个有一个 textView,供用户输入。

当用户点击 textView 时,键盘弹出,有时会盖住 textView。当我尝试垂直滚动时,正如我希望 scrollview 允许的那样,scrollview 没有响应,我只能水平滚动到下一个页面视图。

问题在于,当用户点击textview弹出键盘时,textview可能隐藏在键盘下方,因此用户看不到他正在输入的内容。我希望在点击键盘时向上滚动视图,以便用户可以看到正在键入的内容。

这就是所谓的"Keyboard managing" 你必须:

1) 设置键盘出现和键盘消失动作的观察者

2) 当键盘出现时更新底部约束。

3) 键盘消失后再次更新

4) 删除观察者,此时view会消失

分步说明,例如此处:Move view with keyboard using Swift

但我的建议是按照说明更新约束,而不是起源。或者,您可以为 content

设置一个偏移量

您应该在堆栈视图中设置底部约束 然后像这样控制将它拖到 class 中:

class yourClassViewController: UIViewController {
// MARK: Properties
@IBOutlet weak var bottomConstraint: NSLayoutConstraint!

然后在你的 viewDidLoad 方法中这样写:

 override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.keyboardNotification(notification:)),
                                           name: NSNotification.Name.UIKeyboardWillChangeFrame,
                                           object: nil)


    // Do any additional setup after loading the view.
}

及以下:

   deinit {
    NotificationCenter.default.removeObserver(self)
}

@objc func keyboardNotification(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
        let endFrameY = endFrame?.origin.y ?? 0
        let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
        let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
        let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
        let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
        if endFrameY >= UIScreen.main.bounds.size.height {
            self.bottomConstraint?.constant = 0.0
        } else {
            self.bottomConstraint?.constant = endFrame?.size.height ?? 0.0
        }
        UIView.animate(withDuration: duration,
                       delay: TimeInterval(0),
                       options: animationCurve,
                       animations: { self.view.layoutIfNeeded() },
                       completion: nil)
    }
}

对于touchsBegan方法中的键盘消失你应该这样写:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
          self.view.endEditing(true)
}

使用通知观察器处理键盘显示和隐藏事件:

// keyboard will show
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowAction(_:)), name: .UIKeyboardWillShow, object: nil)

// keyboard will hide
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideAction(_:)), name: .UIKeyboardWillHide, object: nil)

您可以在键盘即将出现时执行以下操作:

// keyboard will show action
@objc private func keyboardWillShowAction(_ notification: NSNotification) {

    currentScrollViewOffset = scrollView.contentOffset

    let keyboardFrame = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue
    let keyboardHeight = keyboardFrame?.cgRectValue.height
    let padding: CGFloat = 32

    // if text field view would be obstructed by keyboard, apply minimum-needed scroll view offset
    if activeTextFieldMaxYOnScreen! > (UIScreen.main.bounds.height - keyboardHeight! - padding) {

        let temporaryOffset = activeTextFieldMaxYOnScreen! - (UIScreen.main.bounds.height - (keyboardHeight! + padding))
        scrollView.setContentOffset(CGPoint(x: 0, y: currentScrollViewOffset.y + temporaryOffset), animated: true)

    }

    scrollView.addGestureRecognizer(tapToDismissKeyboard)
    scrollView.isScrollEnabled = false

}

然后在键盘关闭时恢复:

// keyboard will hide action
@objc private func keyboardWillHideAction(_ notification: NSNotification) {

    scrollView.setContentOffset(currentScrollViewOffset, animated: true)
    scrollView.removeGestureRecognizer(tapToDismissKeyboard)
    scrollView.isScrollEnabled = true

}

deinit 方法中删除观察者也是一个好习惯:

NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)

请注意,currentScrollViewOffsetactiveTextFieldMaxYOnScreen 是视图控制器的实例属性。要获取 activeTextFieldMaxYOnScreen 值,您可以从文本字段委托中获取它:

// text field should begin editing
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {

    // convert text field line's max-y to perceived max-y on the screen
    let line = textField.viewWithTag(123)
    activeTextFieldMaxYOnScreen = (line?.convert((line?.bounds.origin)!, to: nil).y)! + (line?.frame.height)!
    return true

}