在iOS UIView Swift中为扩展绘图设置动画

时间:2017-07-22 21:39:34

标签: ios swift animation uiview calayer

我想使用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中放置一个断点表明它只执行一次。

编辑:从本质上讲,我问这个问题:我们如何创建自定义UIView并使用UIView.animate()为该视图的渲染设置动画?让我们保留UIView.animate()方法,我们如何设置扩展圈(或UIView中的任何其他自定义绘图)的动画?

编辑:这是我创建的自定义类,只绘制一个三角形。当我为其边界设置动画时,三角形会与视图的基础图像缩放,但在动画期间不会调用绘制方法。我不想要那个;我希望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()
}

}

3 个答案:

答案 0 :(得分:6)

我一直在处理你的问题,这种方法正在使用CABasicAnimationCAShapeLayer我定义了一个自定义UIView类来完成工作,我将添加进一步的改进{{ 1}}和@IBDesignable

@IBInspectables

<强> 结果

TRON Github page

希望这有帮助

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


}

<强>结果

enter image description here

希望这有助于你

答案 2 :(得分:1)

一个中有两个答案。首先,如果您确实希望每次传递都调用drawRect,则应使用CADisplayLink并手动设置视图框架的动画。 第二:如果你不想这样做但仍希望它顺利绘制,那么将contentMode设置为scaleAspectFit。这将使其与UIView.animateWithDuration平滑地绘制。但是,它不会像drawRect解决方案那样在每个周期调用CADisplayLink

使用CADisplayLink更新每帧的边界。添加needsDisplayOnBoundsChange = true,以便在更改边界时重绘视图。

http://i.imgur.com/JjarYsq.gif

enter image description here

示例:

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

相关问题