我想使用CGContext在UIView中创建一个自定义动态绘图,这样当我在其上执行UIView动画时,绘图将在动画/插值过程中呈现。我尝试使用自定义CALayer并覆盖其绘制方法,但失败了。我尝试了子类化UIView绘图(_ in:CGRect),但这只是第一次绘制。这就是我想要做的事情:
class RadioWaveView : UIView {
override func draw(_ in:CGRect) {
// draw something, for example an ellipse based on frame size, something like this:
// let context = CGGraphicsCurrentContext()
// context.addEllipse(self.frame)
// Unfortunately this method is called only once, not during animation
}
}
viewDidAppear()
中的:
UIView.animate(withDuration: 5, delay: 1, options: [.repeat], animations: {
self.myRadioWaveView.frame = CGRect(x:100,y:100,width:200,heigh:300)
}, completion: { flag in
})
动画有效,因为我设置了myRadioWaveView
的背景颜色并看到它在屏幕上展开。在draw
中放置一个断点表明它只执行一次。
编辑:这是我创建的自定义类,只绘制一个三角形。当我为其边界设置动画时,三角形会与视图的基础图像缩放,但在动画期间不会调用绘制方法。我不想要那个;我希望draw()方法在动画的每一帧重绘图形。
open class TriangleView : UIView {
override open func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x:self.frame.width,y:0))
path.addLine(to: CGPoint(x:self.frame.width/2,y:self.frame.height))
path.addLine(to: CGPoint.zero)
path.closeSubpath()
context.beginPath()
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(3)
context.addPath(path)
context.closePath()
context.strokePath()
}
}
答案 0 :(得分:6)
我一直在处理你的问题,这种方法正在使用CABasicAnimation
和CAShapeLayer
我定义了一个自定义UIView
类来完成工作,我将添加进一步的改进{{ 1}}和@IBDesignable
@IBInspectables
<强> 结果 强>
希望这有帮助
答案 1 :(得分:2)
使用这种最简单的方法
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var animatableView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Circle
//animatableView.layer.cornerRadius = animatableView.bounds.size.height/2
//animatableView.layer.masksToBounds = true
//triangle
self.applyTriangle(givenView: animatableView)
UIView.animate(withDuration: 2, delay: 1, options: [.repeat], animations: {
self.animatableView.transform = CGAffineTransform(scaleX: 2, y: 2)
}) { (finished) in
self.animatableView.transform = CGAffineTransform.identity
}
}
func applyTriangle(givenView: UIView){
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: givenView.bounds.width / 2, y: givenView.bounds.width))
bezierPath.addLine(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: givenView.bounds.width, y: 0))
bezierPath.close()
let shapeLayer = CAShapeLayer(layer: givenView.layer)
shapeLayer.path = bezierPath.cgPath
shapeLayer.frame = givenView.bounds
shapeLayer.masksToBounds = true
givenView.layer.mask = shapeLayer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
<强>结果
希望这有助于你
答案 2 :(得分:1)
一个中有两个答案。首先,如果您确实希望每次传递都调用drawRect
,则应使用CADisplayLink
并手动设置视图框架的动画。
第二:如果你不想这样做但仍希望它顺利绘制,那么将contentMode设置为scaleAspectFit。这将使其与UIView.animateWithDuration
平滑地绘制。但是,它不会像drawRect
解决方案那样在每个周期调用CADisplayLink
。
使用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。