ios - 如何使用 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
])
当键盘出现/消失时,我需要该视图上下键盘动画
解决方案
我已经创建了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
防止在其他屏幕上进行交互。
约束值通过收听更新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 = ($0.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
)
}
}
推荐阅读
- bash - 如何获得 2 行表示字符但具有不同二进制值的行,以便被 uniq 识别为重复项
- r - 什么类型的分析具有连续和分类预测变量的分类结果?
- javascript - 如何立即停止设置为间隔的功能
- css - autoprefixer 能否解决 Internet Explorer 的 CSS 变量问题?
- c# - 步骤定义文件错误
- r - 根据数据帧的单独向量中大于 x 的值替换一个向量中的值
- apache-kafka - 如果 RocksDB 在内存中缓冲,为什么要在 Kafka Streams Processor API 中启用记录缓存?
- azure - OAuth2 配置问题:AADSTS50011:请求中指定的回复 url 与为应用程序配置的回复 url 不匹配:
- javascript - 如何删除未命名/已标识的元素并更改另一个父元素属性?
- clojure - Clojure 从函数返回简单的真或假