gpt4 book ai didi

ios - 使用约束对 UIView 层进行动画处理(自动布局动画)

转载 作者:行者123 更新时间:2023-11-28 23:38:39 25 4
gpt4 key购买 nike

我正在做一个项目,我需要为由阴影、渐变和圆角(不是所有角)组成的 View 高度设置动画。所以我使用了 View 的 layerClass 属性。

下面是简单的示例演示。问题是,当我通过修改其约束来更改 View 高度时,会导致图层类立即出现动画,这有点尴尬。

下面是我的示例代码

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

}

}

在动画 View 时,它会调用它的 layoutSubviews() 方法,这将导致重新计算阴影层的边界。

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

这个方法对吗?或者有更好的方法吗?

最佳答案

我知道这是一个棘手的问题。实际上,您根本不需要关心 layoutSubViews。这里的关键是当你设置 shapeLayer 的时候。如果它设置得很好,即在约束全部起作用之后,您不需要在动画期间关心它。

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

 func updateLayer(){
guard let layer = self.layer as? CAShapeLayer else { return }
layer.path = UIBezierPath(roundedRect: layer.bounds, cornerRadius: 10).cgPath
layer.shadowPath = layer.path
}

// 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
// }
// }

在 ViewController 中:添加 viewDidAppear() 并移除其他动画 block

  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
}
....

那么你就可以开始了。祝你有美好的一天。

关于ios - 使用约束对 UIView 层进行动画处理(自动布局动画),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54135618/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com