在 Swift 中动画自定义 UIView 属性
Posted
技术标签:
【中文标题】在 Swift 中动画自定义 UIView 属性【英文标题】:Animate Custom UIView Property in Swift 【发布时间】:2016-05-26 19:07:40 【问题描述】:全部!
我使用CoreGraphics
创建了一个循环进度视图,其外观和更新如下:
50% 75%
该类是一个UIView
类,它有一个名为“progress”的变量,用于确定圆圈的填充量。
效果很好,但我希望能够对进度变量的更改进行动画处理,以使条形图平滑地动画化。
我从无数示例中读到,我需要一个 CALayer
类以及我创建的 View 类,但是它根本没有动画。
两个问题:
-
我可以保留我在
CoreGraphics
中绘制的图形,还是需要以某种方式在CALayer
中重新绘制它?
我当前(尝试的)解决方案在以下位置崩溃:anim.fromValue = pres.progress
。怎么了?
class CircleProgressView: UIView
@IBInspectable var backFillColor: UIColor = UIColor.blueColor()
@IBInspectable var fillColor: UIColor = UIColor.greenColor()
@IBInspectable var strokeColor: UIColor = UIColor.greenColor()
dynamic var progress: CGFloat = 0.00
didSet
self.layer.setValue(progress, forKey: "progress")
var distToDestination: CGFloat = 10.0
@IBInspectable var arcWidth: CGFloat = 20
@IBInspectable var outlineWidth: CGFloat = 5
override class func layerClass() -> AnyClass
return CircleProgressLayer.self
override func drawRect(rect: CGRect)
var fillColor = self.fillColor
if distToDestination < 3.0
fillColor = UIColor.greenColor()
else
fillColor = self.fillColor
//Drawing the inside of the container
//Drawing in the container
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
let radius: CGFloat = max(bounds.width, bounds.height) - 10
let startAngle: CGFloat = 3 * π / 2
let endAngle: CGFloat = 3 * π / 2 + 2 * π
let path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.lineWidth = arcWidth
backFillColor.setStroke()
path.stroke()
let fill = UIColor.blueColor().colorWithAlphaComponent(0.15)
fill.setFill()
path.fill()
//Drawing the fill path. Same process
let fillAngleLength = (π) * progress
let fillStartAngle = 3 * π / 2 - fillAngleLength
let fillEndAngle = 3 * π / 2 + fillAngleLength
let fillPath_fill = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: fillStartAngle, endAngle: fillEndAngle, clockwise: true)
fillPath_fill.lineWidth = arcWidth
fillColor.setStroke()
fillPath_fill.stroke()
//Drawing container outline on top
let outlinePath_outer = UIBezierPath(arcCenter: center, radius: radius / 2 - outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let outlinePath_inner = UIBezierPath(arcCenter: center, radius: radius / 2 - arcWidth + outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
outlinePath_outer.lineWidth = outlineWidth
outlinePath_inner.lineWidth = outlineWidth
strokeColor.setStroke()
outlinePath_outer.stroke()
outlinePath_inner.stroke()
class CircleProgressLayer: CALayer
@NSManaged var progress: CGFloat
override class func needsDisplayForKey(key: String) -> Bool
if key == "progress"
return true
return super.needsDisplayForKey(key)
override func actionForKey(key: String) -> CAAction?
if (key == "progress")
if let pres = self.presentationLayer()
let anim: CABasicAnimation = CABasicAnimation.init(keyPath: key)
anim.fromValue = pres.progress
anim.duration = 0.2
return anim
return super.actionForKey(key)
else
return super.actionForKey(key)
感谢您的帮助!
【问题讨论】:
你有没有试过做类似的事情:informit.com/articles/article.aspx?p=1431312&seqNum=5? 我相信这就是 actionForKey() -> CAAction 中发生的事情? 【参考方案1】:试试这个:)
class ViewController: UIViewController
let progressView = CircleProgressView(frame:CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200))
override func viewDidLoad()
super.viewDidLoad()
let button = UIButton()
button.frame = CGRectMake(0, 300, 200, 100)
button.backgroundColor = UIColor.yellowColor()
button.addTarget(self, action: #selector(ViewController.tap), forControlEvents: UIControlEvents.TouchUpInside)
view.addSubview(button)
view.addSubview(progressView)
progressView.progress = 1.0
func tap()
if progressView.progress == 0.5
progressView.progress = 1.0
else
progressView.progress = 0.5
class CircleProgressView: UIView
dynamic var progress: CGFloat = 0.00
didSet
let animation = CABasicAnimation()
animation.keyPath = "progress"
animation.fromValue = circleLayer().progress
animation.toValue = progress
animation.duration = Double(0.5)
self.layer.addAnimation(animation, forKey: "progress")
circleLayer().progress = progress
func circleLayer() -> CircleProgressLayer
return self.layer as! CircleProgressLayer
override class func layerClass() -> AnyClass
return CircleProgressLayer.self
class CircleProgressLayer: CALayer
@NSManaged var progress: CGFloat
override class func needsDisplayForKey(key: String) -> Bool
if key == "progress"
return true
return super.needsDisplayForKey(key)
var backFillColor: UIColor = UIColor.blueColor()
var fillColor: UIColor = UIColor.greenColor()
var strokeColor: UIColor = UIColor.greenColor()
var distToDestination: CGFloat = 10.0
var arcWidth: CGFloat = 20
var outlineWidth: CGFloat = 5
override func drawInContext(ctx: CGContext)
super.drawInContext(ctx)
UIGraphicsPushContext(ctx)
//Drawing in the container
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
let radius: CGFloat = max(bounds.width, bounds.height) - 10
let startAngle: CGFloat = 3 * CGFloat(M_PI) / 2
let endAngle: CGFloat = 3 * CGFloat(M_PI) / 2 + 2 * CGFloat(M_PI)
let path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.lineWidth = arcWidth
backFillColor.setStroke()
path.stroke()
let fill = UIColor.blueColor().colorWithAlphaComponent(0.15)
fill.setFill()
path.fill()
//Drawing the fill path. Same process
let fillAngleLength = (CGFloat(M_PI)) * progress
let fillStartAngle = 3 * CGFloat(M_PI) / 2 - fillAngleLength
let fillEndAngle = 3 * CGFloat(M_PI) / 2 + fillAngleLength
let fillPath_fill = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: fillStartAngle, endAngle: fillEndAngle, clockwise: true)
fillPath_fill.lineWidth = arcWidth
fillColor.setStroke()
fillPath_fill.stroke()
//Drawing container outline on top
let outlinePath_outer = UIBezierPath(arcCenter: center, radius: radius / 2 - outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let outlinePath_inner = UIBezierPath(arcCenter: center, radius: radius / 2 - arcWidth + outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
outlinePath_outer.lineWidth = outlineWidth
outlinePath_inner.lineWidth = outlineWidth
strokeColor.setStroke()
outlinePath_outer.stroke()
outlinePath_inner.stroke()
UIGraphicsPopContext()
【讨论】:
谢谢!我知道提供示例代码可能会令人不悦,但这就像一个魅力。看起来我在错误的地方有正确的元素。感谢您向我展示如何在 CA 层中绘制核心图形元素,并将动画嵌入到 didSet 中:) 干得好,我真的尝试了很多次才能让它工作,最重要的是用 @NSManaged 标记您的自定义属性【参考方案2】:虽然 AntonTheDev 提供了一个很好的答案,但他的解决方案不允许您在动画块中为 CircularProgressView 设置动画,因此您不能做以下整洁的事情:
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut,
animations:
circularProgress.progress = 0.76
, completion: nil)
这里有一个类似的问题,根据接受的答案和this 帖子的想法,提供了最新的 Swift 3 答案。 这就是最终解决方案的样子。
Swift 3 Solution
【讨论】:
以上是关于在 Swift 中动画自定义 UIView 属性的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 animator() [ OSX ] 为 Swift 自定义属性设置动画
UIView beginAnimations 在自定义属性上太快了