Ios 为UIView设置动画';带约束的s层(自动布局动画)

Ios 为UIView设置动画';带约束的s层(自动布局动画),ios,animation,autolayout,calayer,Ios,Animation,Autolayout,Calayer,我在项目中的工作,我需要动画的高度,其中包括阴影,梯度和圆角(不是所有的角落)。 所以我使用了视图的layerClass属性 下面是一个简单的示例演示。 问题是,当我通过修改视图的约束来更改视图的高度时,它会立即生成层类的动画,这有点尴尬 下面是我的示例代码 import UIKit class CustomView: UIView{ var isAnimating: Bool = false override init(frame: CGRect) { s

我在项目中的工作,我需要动画的高度,其中包括阴影,梯度和圆角(不是所有的角落)。 所以我使用了视图的
layerClass
属性

下面是一个简单的示例演示。 问题是,当我通过修改视图的约束来更改视图的高度时,它会立即生成层类的动画,这有点尴尬

下面是我的示例代码

import UIKit

class CustomView: UIView{

    var isAnimating: Bool = false

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupView()
    }

    func setupView(){

        self.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)

        guard let layer = self.layer as? CAShapeLayer else { return }

        layer.fillColor = UIColor.green.cgColor
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
        layer.shadowOpacity = 0.6
        layer.shadowRadius = 5

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // While animating `myView` height, this method gets called
        // So new bounds for layer will be calculated and set immediately
        // This result in not proper animation

        // check by making below condition always true

        if !self.isAnimating{ //if true{
            guard let layer = self.layer as? CAShapeLayer else { return }

            layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
            layer.shadowPath = layer.path
        }
    }

}

class TestViewController : UIViewController {

    // height constraint for animating height
    var heightConstarint: NSLayoutConstraint?

    var heightToAnimate: CGFloat = 200

    lazy var myView: CustomView = {
        let view = CustomView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .clear
        return view
    }()

    lazy var mySubview: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .yellow
        return view
    }()

    lazy var button: UIButton = {
        let button = UIButton(frame: .zero)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Animate", for: .normal)
        button.setTitleColor(.black, for: .normal)
        button.addTarget(self, action: #selector(self.animateView(_:)), for: .touchUpInside)
        return button
    }()


    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .white
        self.view.addSubview(self.myView)
        self.myView.addSubview(self.mySubview)
        self.view.addSubview(self.button)

        self.myView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor).isActive = true
        self.myView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor).isActive = true
        self.myView.topAnchor.constraint(equalTo: self.view.layoutMarginsGuide.topAnchor, constant: 100).isActive = true
        self.heightConstarint = self.myView.heightAnchor.constraint(equalToConstant: 100)
        self.heightConstarint?.isActive = true

        self.mySubview.leadingAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.leadingAnchor).isActive = true
        self.mySubview.trailingAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.trailingAnchor).isActive = true
        self.mySubview.topAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.topAnchor).isActive = true
        self.mySubview.bottomAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.bottomAnchor).isActive = true

        self.button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        self.button.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor, constant: -20).isActive = true
    }

    @objc func animateView(_ sender: UIButton){

        CATransaction.begin()
        CATransaction.setAnimationDuration(5.0)
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))

        UIView.animate(withDuration: 5.0, animations: {

            self.myView.isAnimating = true
            self.heightConstarint?.constant = self.heightToAnimate
            // this will call `myView.layoutSubviews()`
            // and layer's new bound will set automatically
            // this causing layer to be directly jump to height 200, instead of smooth animation
            self.view.layoutIfNeeded()

        }) { (success) in
            self.myView.isAnimating = false
        }

        let shadowPathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.shadowPath))
        let pathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))

        let toValue = UIBezierPath(
            roundedRect:CGRect(x: 0, y: 0, width: self.myView.bounds.width, height: heightToAnimate),
            cornerRadius: 10
        ).cgPath


        shadowPathAnimation.fromValue = self.myView.layer.shadowPath
        shadowPathAnimation.toValue = toValue

        pathAnimation.fromValue = (self.myView.layer as! CAShapeLayer).path
        pathAnimation.toValue = toValue


        self.myView.layer.shadowPath = toValue
        (self.myView.layer as! CAShapeLayer).path = toValue

        self.myView.layer.add(shadowPathAnimation, forKey: #keyPath(CAShapeLayer.shadowPath))
        self.myView.layer.add(pathAnimation, forKey: #keyPath(CAShapeLayer.path))

        CATransaction.commit()

    }

}
设置视图动画时,它将调用其
layoutSubviews()
方法,这将导致重新计算阴影层的边界

所以我检查了视图当前是否正在设置动画,然后不重新计算阴影层的边界


这种方法正确吗?或者有更好的方法吗?

我知道这是一个棘手的问题。实际上,您根本不需要关心布局子视图。这里的关键是设置shapeLayer时。如果设置得很好,即在所有约束都起作用之后,则在动画期间无需关心这一点

//在CustomView中,注释掉layoutSubViews()并添加updateLayer()

在ViewController中:添加ViewDidDisplay()并删除其他is动画块

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    myView.updateLayer()
}

@objc func animateView(_ sender: UIButton){

    CATransaction.begin()
    CATransaction.setAnimationDuration(5.0)
    CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))

    UIView.animate(withDuration: 5.0, animations: {

        self.heightConstarint?.constant = self.heightToAnimate
        // this will call `myView.layoutSubviews()`
        // and layer's new bound will set automatically
        // this causing layer to be directly jump to height 200, instead of smooth animation
        self.view.layoutIfNeeded()

    }) { (success) in
        self.myView.isAnimating = false
    } 
   ....

那你就可以走了。祝你度过愉快的一天。

下面的代码对我也很有用,因为我想使用没有任何标志的布局子视图

UIView.animate(withDuration: 5.0, animations: {

            let shadowPathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.shadowPath))
            let pathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))

            let toValue = UIBezierPath(
                roundedRect:CGRect(x: 0, y: 0, width: self.myView.bounds.width, height: self.heightToAnimate),
                cornerRadius: 10
                ).cgPath


            shadowPathAnimation.fromValue = self.myView.layer.shadowPath
            shadowPathAnimation.toValue = toValue

            pathAnimation.fromValue = (self.myView.layer as! CAShapeLayer).path
            pathAnimation.toValue = toValue

            self.heightConstarint?.constant = self.heightToAnimate
            self.myView.layoutIfNeeded()

            self.myView.layer.shadowPath = toValue
            (self.myView.layer as! CAShapeLayer).path = toValue

            self.myView.layer.add(shadowPathAnimation, forKey: #keyPath(CAShapeLayer.shadowPath))
            self.myView.layer.add(pathAnimation, forKey: #keyPath(CAShapeLayer.path))

            CATransaction.commit()

        })
并覆盖
layoutSubview
,如下所示

override func layoutSubviews() {
        super.layoutSubviews()

        guard let layer = self.layer as? CAShapeLayer else { return }

        layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
        layer.shadowPath = layer.path
    }
override func layoutSubviews() {
        super.layoutSubviews()

        guard let layer = self.layer as? CAShapeLayer else { return }

        layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
        layer.shadowPath = layer.path
    }