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
}