是否可以在滚动视图中使用 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)
请注意,currentScrollViewOffset
和 activeTextFieldMaxYOnScreen
是视图控制器的实例属性。要获取 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
}
我有一个 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)
请注意,currentScrollViewOffset
和 activeTextFieldMaxYOnScreen
是视图控制器的实例属性。要获取 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
}