我知道CALayer的shadowPath只能使用显式动画制作动画,但我仍然无法使其工作。我怀疑我没有正确传递toValue
- 据我所知,这必须是id
,但该属性需要一个CGPathRef。将其存储在UIBezierPath
中似乎不起作用。我使用以下代码进行测试:
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
theAnimation.duration = 3.0;
theAnimation.toValue = [UIBezierPath bezierPathWithRect:CGRectMake(-10.0, -10.0, 50.0, 50.0)];
[self.view.layer addAnimation:theAnimation forKey:@"animateShadowPath"];
(我使用负值,以确保阴影延伸到位于其顶部的视图之外......图层的masksToBounds
属性设置为NO
)。
如何实现shadowPath的动画?
更新
问题几乎解决了。不幸的是,主要问题是有点粗心的错误......
我犯的错误是将动画添加到视图控制器的根层,而不是我专用于阴影的图层。另外,@ pe8ter是正确的,因为toValue
需要CGPathRef
强制转换为id
(显然当我尝试过此操作之前,由于错误的图层错误,我仍然没有动画) 。动画使用以下代码:
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
theAnimation.duration = 3.0;
theAnimation.toValue = (id)[UIBezierPath bezierPathWithRect:myRect].CGPath;
[controller.shadowLayer addAnimation:theAnimation forKey:@"shadowPath"];
我很欣赏我提供的示例代码很难发现。希望它仍然可以用于处于类似情况的人。
然而,当我尝试添加行
时controller.shadowLayer.shadowPath = [UIBezierPath bezierPathWithRect:myRect].CGPath;
动画停止工作,阴影立即跳到最终位置。文档说添加动画时使用与要更改的属性相同的键,以覆盖设置属性值时创建的隐式动画,但是shadowPath无法生成隐式动画...所以如何获取新属性 动画之后
答案 0 :(得分:9)
首先,您没有设置动画的fromValue
。
其次,你是对的:toValue
接受CGPathRef
,但需要将其转换为id
。做这样的事情:
theAnimation.toValue = (id)[UIBezierPath bezierPathWithRect:newRect].CGPath;
如果您希望在动画之后保留更改,则还需要明确设置图层的shadowPath
属性。
答案 1 :(得分:1)
let cornerRadious = 10.0
//
let shadowPathFrom = UIBezierPath(roundedRect: rect1, cornerRadius: cornerRadious)
let shadowPathTo = UIBezierPath(roundedRect: rect2, cornerRadius: cornerRadious)
//
layer.masksToBounds = false
layer.shadowColor = UIColor.yellowColor().CGColor
layer.shadowOpacity = 0.6
//
let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowAnimation.fromValue = shadowPathFrom.CGPath
shadowAnimation.toValue = shadowPathTo.CGPath
shadowAnimation.duration = 0.4
shadowAnimation.autoreverses = true
shadowAnimation.removedOnCompletion = true
shadowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
layer.addAnimation(shadowAnimation, forKey: "shadowAnimation")
答案 2 :(得分:0)
我在阴影动画方面遇到了同样的问题,并找到了在动画结束后持久保留阴影的解决方案。开始动画之前,您需要为阴影层设置最终路径。之所以会恢复初始阴影,是因为CoreAnimation不会更新原始图层的属性,而是会创建该图层的副本并在此副本上显示动画(check Neko1kat answer for more details)。动画结束后,系统删除该动画层并返回原始层,该层尚未更新路径,并且会出现旧的阴影。尝试以下代码:
let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowAnimation.fromValue = currentShadowPath
shadowAnimation.toValue = newShadowPath
shadowAnimation.duration = 3.0
shadowLayer.shadowPath = newShadowPath
shadowLayer.add(shadowAnimation, forKey: "shadowAnimation")
答案 3 :(得分:0)
虽然不直接回答这个问题,但是如果你只是需要一个view可以投下阴影,可以直接使用我的类。
/*
Shadow.swift
Copyright © 2018, 2020-2021 BB9z
https://github.com/BB9z/iOS-Project-Template
The MIT License
https://opensource.org/licenses/MIT
*/
/**
A view drops shadow.
*/
@IBDesignable
class ShadowView: UIView {
@IBInspectable var shadowOffset: CGPoint = CGPoint(x: 0, y: 8) {
didSet { needsUpdateStyle = true }
}
@IBInspectable var shadowBlur: CGFloat = 10 {
didSet { needsUpdateStyle = true }
}
@IBInspectable var shadowSpread: CGFloat = 0 {
didSet { needsUpdateStyle = true }
}
/// Set nil can disable shadow
@IBInspectable var shadowColor: UIColor? = UIColor.black.withAlphaComponent(0.3) {
didSet { needsUpdateStyle = true }
}
@IBInspectable var cornerRadius: CGFloat {
get { layer.cornerRadius }
set { layer.cornerRadius = newValue }
}
private var needsUpdateStyle = false {
didSet {
guard needsUpdateStyle, !oldValue else { return }
DispatchQueue.main.async { [self] in
if needsUpdateStyle { updateLayerStyle() }
}
}
}
private func updateLayerStyle() {
needsUpdateStyle = false
if let color = shadowColor {
Shadow(view: self, offset: shadowOffset, blur: shadowBlur, spread: shadowSpread, color: color, cornerRadius: cornerRadius)
} else {
layer.shadowColor = nil
layer.shadowPath = nil
layer.shadowOpacity = 0
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
updateLayerStyle()
}
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
lastLayerSize = layer.bounds.size
if shadowColor != nil, layer.shadowOpacity == 0 {
updateLayerStyle()
}
}
private var lastLayerSize = CGSize.zero {
didSet {
if oldValue == lastLayerSize { return }
guard shadowColor != nil else { return }
updateShadowPathWithAnimationFixes(bonuds: layer.bounds)
}
}
// We needs some additional step to achieve smooth result when view resizing
private func updateShadowPathWithAnimationFixes(bonuds: CGRect) {
let rect = bonuds.insetBy(dx: shadowSpread, dy: shadowSpread)
let newShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
if let resizeAnimation = layer.animation(forKey: "bounds.size") {
let key = #keyPath(CALayer.shadowPath)
let shadowAnimation = CABasicAnimation(keyPath: key)
shadowAnimation.duration = resizeAnimation.duration
shadowAnimation.timingFunction = resizeAnimation.timingFunction
shadowAnimation.fromValue = layer.shadowPath
shadowAnimation.toValue = newShadowPath
layer.add(shadowAnimation, forKey: key)
}
layer.shadowPath = newShadowPath
}
}
/**
Make shadow with the same effect as Sketch app.
*/
func Shadow(view: UIView?, offset: CGPoint, blur: CGFloat, spread: CGFloat, color: UIColor, cornerRadius: CGFloat = 0) { // swiftlint:disable:this identifier_name
guard let layer = view?.layer else {
return
}
layer.shadowColor = color.cgColor
layer.shadowOffset = CGSize(width: offset.x, height: offset.y)
layer.shadowRadius = blur
layer.shadowOpacity = 1
layer.cornerRadius = cornerRadius
let rect = layer.bounds.insetBy(dx: spread, dy: spread)
layer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
}
通过https://github.com/BB9z/iOS-Project-Template/blob/master/App/General/Effect/Shadow.swift