UIKit:如何使用以编程方式创建的约束为约束更改设置动画?

UIKit: how to animate a constraint change with a programmatically created constraint?

我已经按照 this answer 从 XIB 文件中实例化了一个视图:

extension UIView {    
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil) else {
            fatalError("missing expected nib named: \(name)")
        }
        guard let view = nib.first as? Self else {
            fatalError("view of type \(Self.self) not found in \(nib)")
        }
        return view
    }
}

现在我想创建一个根据内容自适应高度的视图:

class CustomTopView: UIView {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var subtitleLabel: UILabel!
    
    static func instance(withTitle title: NSAttributedString, subtitle: NSAttributedString) -> CustomTopView {
        let customTopView = CustomTopView.fromNib()
        customTopView.titleLabel.attributedText = title
        customTopView.subtitleLabel.attributedText = subtitle
        return customTopView
    }
}

我想做的是从屏幕顶部动画显示此视图,就像通知弹出窗口一样。我写了这个:

extension UIView {
    func showCustomTopView(withTitle title: NSAttributedString, subtitle: NSAttributedString) {
        let customTopView = CustomTopView.instance(withTitle: title, subtitle: subtitle)
        addSubview(customTopView)
        customTopView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            customTopView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
            customTopView.constraint(equalTo: customTopView.trailingAnchor, constant: 20.0),
        ])
        customTopView.layoutIfNeeded() // *
        NSLayoutConstraint.activate([customTopView.topAnchor.constraint(equalTo: topAnchor, constant: -customTopView.bounds.height)])
    }
}

我这里有两个问题。最主要的是我不知道如何从这里执行我想要的动画,以便视图最终可见。我在最后一个 NSLayoutConstraint.activate(...):

之后尝试了这个
UIView.animate(withDuration: 0.5) {
    self.topAnchor.constraint(equalTo: customTopView.topAnchor, constant: 100.0).isActive = true
    self.layoutIfNeeded()
}

但是弹出窗口没有按预期显示,而是从屏幕顶部 100px 处开始,然后上升并消失。我做错了什么?

此外,我不确定标有 // * 的行:我写这行是为了获取正确的 customTopView 高度,但我不确定这是正确的方法.

感谢您的帮助!

一种方法:

  • 创建 一个 约束设置 customTopView 的底部在视图的顶部上方
  • 创建一个 second 约束设置 customTopView 的顶部低于视图的顶部
  • 添加customTopView作为子视图,激活第一个约束
  • 通过停用第一个约束并激活第二个约束,将 customTopView 设置为动画

这是您的 showCustomTopView(...) 扩展的修改版本,使用 UILabel 作为 customTopView -- 应该可以很好地与您的 CustomTopView.instance(withTitle: ...):

extension UIView {
    func showCustomTopView(withTitle title: NSAttributedString, subtitle: NSAttributedString) {
        let customTopView = UILabel()
        customTopView.text = title.string
        customTopView.backgroundColor = .green
        addSubview(customTopView)
        customTopView.translatesAutoresizingMaskIntoConstraints = false
        let g = self.safeAreaLayoutGuide
        // constraint: Bottom of customTopView to Top of self Minus 8-pts
        let cvBot = customTopView.bottomAnchor.constraint(equalTo: topAnchor, constant: -8.0)
        // constraint: Top of customTopView to Top of self Plus 8-pts
        let cvTop = customTopView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0)
        NSLayoutConstraint.activate([
            customTopView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
            customTopView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
            // activate cvBot, so customTopView is 8-pts above the top of self
            cvBot,
        ])
        // execute this async, so the initial view position is set
        DispatchQueue.main.async {
            // deactivate the Bottom constraint
            cvBot.isActive = false
            // activate the Top constraint
            cvTop.isActive = true
            // animate it into view
            UIView.animate(withDuration: 0.5, animations: {
                self.layoutIfNeeded()
            })
        }
    }
}