两个答案合二为一。首先,如果您真的希望在每次传递时都调用drawRect,您应该使用CADisplayLink 并手动为视图的框架设置动画。
第二:如果你不想这样做,但仍然希望它绘制流畅,那么将 contentMode 设置为 scaleAspectFit。这将使它与UIView.animateWithDuration 顺利绘制。但是,它不会像 CADisplayLink 解决方案那样在每个循环中调用 drawRect。
使用每帧更新边界的CADisplayLink。添加needsDisplayOnBoundsChange = true 以便在更改边界时重新绘制视图。
http://i.imgur.com/JjarYsq.gif
例子:
//
// ViewController.swift
// StackOverflow
//
// Created by Brandon T on 2017-07-22.
// Copyright © 2017 XIO. All rights reserved.
//
import UIKit
class RadioWave : UIView {
private var dl: CADisplayLink?
private var startTime: CFTimeInterval!
private var fromFrame: CGRect!
private var toFrame: CGRect!
private var duration: CFTimeInterval!
private var completion: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
self.layer.needsDisplayOnBoundsChange = true;
self.fromFrame = frame;
self.toFrame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateToFrame(frame: CGRect, duration: CFTimeInterval, completion:(() -> Void)? = nil) {
self.dl?.remove(from: .current, forMode: .commonModes)
self.dl?.invalidate()
self.dl = CADisplayLink(target: self, selector: #selector(onUpdate(dl:)))
self.completion = completion
self.fromFrame = self.frame
self.toFrame = frame
self.duration = duration
self.startTime = CACurrentMediaTime()
self.dl?.add(to: .current, forMode: .commonModes)
}
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx?.clear(rect)
ctx?.addEllipse(in: rect)
ctx?.clip()
ctx?.setFillColor(UIColor.blue.cgColor)
ctx?.fill(rect)
}
@objc
func onUpdate(dl: CADisplayLink) {
let dt = CGFloat((dl.timestamp - self.startTime) / self.duration)
if (dt > 1.0) {
self.frame = self.toFrame
self.dl?.remove(from: .current, forMode: .commonModes)
self.dl?.invalidate()
self.dl = nil
completion?()
completion = nil
return;
}
var frame: CGRect! = self.toFrame;
frame.origin.x = (self.toFrame.origin.x - self.fromFrame.origin.x) * dt + fromFrame.origin.x
frame.origin.y = (self.toFrame.origin.y - self.fromFrame.origin.y) * dt + fromFrame.origin.y
frame.size.width = (self.toFrame.size.width - self.fromFrame.size.width) * dt + fromFrame.size.width
frame.size.height = (self.toFrame.size.height - self.fromFrame.size.height) * dt + fromFrame.size.height
self.frame = frame
}
}
class ViewController: UIViewController {
var radioWave: RadioWave!
override func viewDidLoad() {
super.viewDidLoad()
self.radioWave = RadioWave(frame: CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0))
self.view.addSubview(self.radioWave)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let smallFrame = CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0)
let largeFrame = CGRect(x: self.view.center.x - 100.0, y: self.view.center.y - 100.0, width: 200.0, height: 200.0)
self.radioWave.animateToFrame(frame: largeFrame, duration: 2.0, completion: {
self.radioWave.animateToFrame(frame: smallFrame, duration: 2.0)
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
视图通常仅在其边界发生变化时才会重绘。 iOS 上的动画实际上只是被插值的视图的快照(表示层)。这大大提升了性能,而不是在每个动画步骤上重新绘制和布置视图。
使用[UIView animateWithDuration] 动画圆
class RadioWave : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
self.layer.needsDisplayOnBoundsChange = true
self.contentMode = .scaleAspectFit
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx?.clear(rect)
ctx?.addEllipse(in: rect)
ctx?.clip()
ctx?.setFillColor(UIColor.blue.cgColor)
ctx?.fill(rect)
}
}
class ViewController: UIViewController {
var radioWave: RadioWave!
override func viewDidLoad() {
super.viewDidLoad()
self.radioWave = RadioWave(frame: CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0))
self.view.addSubview(self.radioWave)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let largeFrame = CGRect(x: self.view.center.x - 100.0, y: self.view.center.y - 100.0, width: 200.0, height: 200.0)
self.radioWave.setNeedsDisplay()
UIView.animate(withDuration: 2.0, delay: 0.0, options: [.layoutSubviews, .autoreverse, .repeat], animations: {
self.radioWave.frame = largeFrame
}, completion: { (completed) in
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
上面使用了 contentMode scaleAspectFit,它允许动画正确缩放而不会模糊!这是我所知道的唯一方法,在动画时绘制平滑的圆圈。它不会在每个动画的开始和结束之前调用 drawRect。