ios - 如何使用 iOS 上的约束将 View 附加到键盘顶部边缘

标签 ios swift autolayout uikit nslayoutconstraint

我有一个简单的 View 附加到 View Controller 的底部:

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
])

当键盘在键盘动画之后出现/消失时,我需要该 View 上下移动

最佳答案

我已经创建了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
        )
    }
}

关于ios - 如何使用 iOS 上的约束将 View 附加到键盘顶部边缘,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68780308/

相关文章:

javascript - Materialise Sidenav 不适用于 iOS 或 iPhone,但适用于所有其他设备

iphone - NSPredicate 用于在关系实体中搜索

ios - Swift:将pdf文件读入pdf应用程序阅读器

ios - 添加什么约束使 UIView 占用屏幕大小的一半

ios - swift-无法使用 AutoLayout 在 UIScrollView 中将 UIImageView 居中

ios - 异步将图像从 URL 加载到单元格中的 UIImageView 中不会在初始加载时加载

android - 在flutter中使用setchanged方法时如何禁用ui build

swift if let statement with as?,但为什么要用问号?

ios - 如何同时更新每个自定义tableview单元格数据?

swift - NSLayoutConstraints : How do you correctly center a subview within a view?