如何使用 iOS 上的约束将视图附加到键盘上边缘
How can I attach view to keyboard top edge using constraints on iOS
我在视图控制器的底部附加了一个简单的视图:
let view = UIView()
view.backgroundColor = .gray
view.translatesAutoresizingMaskIntoConstraints = false
let viewBottomPositionConstraint = view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor)
NSLayoutConstraint.activate([
view.heightAnchor.constraint(equalToConstant: 100),
view.leftAnchor.constraint(equalTo: self.view.leftAnchor),
view.rightAnchor.constraint(equalTo: self.view.rightAnchor),
viewBottomPositionConstraint
])
当键盘 appears/disappears 跟随键盘动画
时,我需要那个视图上下移动
我已经创建了KeyboardNotifier
,用法很简单:
var keyboardNotifier: KeyboardNotifier!
override func viewDidLoad() {
super.viewDidLoad()
keyboardNotifier = KeyboardNotifier(parentView: view, constraint: viewBottomPositionConstraint)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
keyboardNotifier.enabled = true
override func viewDidDisappear(_ animated: Bool) {
keyboardNotifier.enabled = false
super.viewDidDisappear(animated)
}
在 viewWillAppear
/viewDidDisappear
中应该是 enabled/disabled 以防止在其他屏幕上进行交互。
通过侦听 keyboardWillChangeFrameNotification
更新约束值
我不得不将它从键盘创建的动画中移出以防止出现故障,并在我自己的动画中并行更新约束。
final class KeyboardNotifier {
var enabled: Bool = true {
didSet {
setNeedsUpdateConstraint()
}
}
init(
parentView: UIView,
constraint: NSLayoutConstraint
) {
self.parentView = parentView
self.constraint = constraint
baseConstant = constraint.constant
notificationObserver = NotificationCenter.default
.addObserver(
forName: UIResponder.keyboardWillChangeFrameNotification,
object: nil,
queue: .main
) { [weak self] in
self?.keyboardEndFrame = ([=11=].userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
self?.setNeedsUpdateConstraint(animationDuration: UIView.inheritedAnimationDuration)
}
}
private weak var parentView: UIView?
private weak var constraint: NSLayoutConstraint?
private let baseConstant: CGFloat
private var notificationObserver: NSObjectProtocol!
private var keyboardEndFrame: CGRect?
private var latestAnimationDuration: TimeInterval?
private func setNeedsUpdateConstraint(animationDuration: TimeInterval = 0) {
guard
latestAnimationDuration == nil
|| animationDuration > latestAnimationDuration!
else { return }
let shouldUpdate = latestAnimationDuration == nil
latestAnimationDuration = animationDuration
if shouldUpdate {
DispatchQueue.main.async {
self.updateConstraint()
}
}
}
private func updateConstraint() {
defer {
latestAnimationDuration = nil
}
guard
let latestAnimationDuration = latestAnimationDuration,
enabled,
let keyboardEndFrame = keyboardEndFrame,
let parentView = parentView,
let constraint = constraint
else { return }
UIView.performWithoutAnimation {
parentView.layoutIfNeeded()
}
let isParentFirstItem = constraint.firstItem is UILayoutGuide || constraint.firstItem === parentView
let followsLayoutGuide = constraint.firstItem is UILayoutGuide || constraint.secondItem is UILayoutGuide
let multiplierSign: CGFloat = isParentFirstItem ? 1 : -1
let screenHeight = UIScreen.main.bounds.height
if keyboardEndFrame.minY >= screenHeight {
constraint.constant = baseConstant
} else {
let safeAreaInsets = (followsLayoutGuide ? parentView.safeAreaInsets.bottom : 0)
// if our constraint makes view invisible when keyboard is hidden, we need to ignore it
let fixedBaseConstant = max(multiplierSign * baseConstant, 0)
constraint.constant = multiplierSign * (screenHeight - keyboardEndFrame.minY - safeAreaInsets + fixedBaseConstant)
}
UIView.animate(
withDuration: latestAnimationDuration,
delay: 0,
options: .beginFromCurrentState,
animations: { parentView.layoutIfNeeded() },
completion: nil
)
}
}
我在视图控制器的底部附加了一个简单的视图:
let view = UIView()
view.backgroundColor = .gray
view.translatesAutoresizingMaskIntoConstraints = false
let viewBottomPositionConstraint = view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor)
NSLayoutConstraint.activate([
view.heightAnchor.constraint(equalToConstant: 100),
view.leftAnchor.constraint(equalTo: self.view.leftAnchor),
view.rightAnchor.constraint(equalTo: self.view.rightAnchor),
viewBottomPositionConstraint
])
当键盘 appears/disappears 跟随键盘动画
时,我需要那个视图上下移动我已经创建了KeyboardNotifier
,用法很简单:
var keyboardNotifier: KeyboardNotifier!
override func viewDidLoad() {
super.viewDidLoad()
keyboardNotifier = KeyboardNotifier(parentView: view, constraint: viewBottomPositionConstraint)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
keyboardNotifier.enabled = true
override func viewDidDisappear(_ animated: Bool) {
keyboardNotifier.enabled = false
super.viewDidDisappear(animated)
}
在 viewWillAppear
/viewDidDisappear
中应该是 enabled/disabled 以防止在其他屏幕上进行交互。
通过侦听 keyboardWillChangeFrameNotification
我不得不将它从键盘创建的动画中移出以防止出现故障,并在我自己的动画中并行更新约束。
final class KeyboardNotifier {
var enabled: Bool = true {
didSet {
setNeedsUpdateConstraint()
}
}
init(
parentView: UIView,
constraint: NSLayoutConstraint
) {
self.parentView = parentView
self.constraint = constraint
baseConstant = constraint.constant
notificationObserver = NotificationCenter.default
.addObserver(
forName: UIResponder.keyboardWillChangeFrameNotification,
object: nil,
queue: .main
) { [weak self] in
self?.keyboardEndFrame = ([=11=].userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
self?.setNeedsUpdateConstraint(animationDuration: UIView.inheritedAnimationDuration)
}
}
private weak var parentView: UIView?
private weak var constraint: NSLayoutConstraint?
private let baseConstant: CGFloat
private var notificationObserver: NSObjectProtocol!
private var keyboardEndFrame: CGRect?
private var latestAnimationDuration: TimeInterval?
private func setNeedsUpdateConstraint(animationDuration: TimeInterval = 0) {
guard
latestAnimationDuration == nil
|| animationDuration > latestAnimationDuration!
else { return }
let shouldUpdate = latestAnimationDuration == nil
latestAnimationDuration = animationDuration
if shouldUpdate {
DispatchQueue.main.async {
self.updateConstraint()
}
}
}
private func updateConstraint() {
defer {
latestAnimationDuration = nil
}
guard
let latestAnimationDuration = latestAnimationDuration,
enabled,
let keyboardEndFrame = keyboardEndFrame,
let parentView = parentView,
let constraint = constraint
else { return }
UIView.performWithoutAnimation {
parentView.layoutIfNeeded()
}
let isParentFirstItem = constraint.firstItem is UILayoutGuide || constraint.firstItem === parentView
let followsLayoutGuide = constraint.firstItem is UILayoutGuide || constraint.secondItem is UILayoutGuide
let multiplierSign: CGFloat = isParentFirstItem ? 1 : -1
let screenHeight = UIScreen.main.bounds.height
if keyboardEndFrame.minY >= screenHeight {
constraint.constant = baseConstant
} else {
let safeAreaInsets = (followsLayoutGuide ? parentView.safeAreaInsets.bottom : 0)
// if our constraint makes view invisible when keyboard is hidden, we need to ignore it
let fixedBaseConstant = max(multiplierSign * baseConstant, 0)
constraint.constant = multiplierSign * (screenHeight - keyboardEndFrame.minY - safeAreaInsets + fixedBaseConstant)
}
UIView.animate(
withDuration: latestAnimationDuration,
delay: 0,
options: .beginFromCurrentState,
animations: { parentView.layoutIfNeeded() },
completion: nil
)
}
}