如何在 iOS 中为自定义属性设置动画

如何在 iOS 中为自定义属性设置动画

问题描述:

我有一个自定义 UIView,它使用 Core Graphics 调用绘制其内容.一切正常,但现在我想为影响显示的值变化设置动画.我有一个自定义属性可以在我的自定义 UView 中实现此目的:

I have a custom UIView that draws its contents using Core Graphics calls. All working well, but now I want to animate a change in value that affects the display. I have a custom property to achieve this in my custom UView:

var _anime: CGFloat = 0
var anime: CGFloat {
    set {
        _anime = newValue
        for(gauge) in gauges {
            gauge.animate(newValue)
        }
        setNeedsDisplay()
    }
    get {
        return _anime
    }
}

而且我已经从 ViewController 开始了一个动画:

And I have started an animation from the ViewController:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.emaxView.anime = 0.5
    UIView.animate(withDuration: 4) {
        DDLogDebug("in animations")
        self.emaxView.anime = 1.0
    }
}

这不起作用 - 动画值确实从 0.5 更改为 1.0,但它会立即更改.有两次对动画设置器的调用,一次值为 0.5,然后立即调用值为 1.0.如果我将我正在制作动画的属性更改为标准 UIView 属性,例如alpha,它工作正常.

This doesn't work - the animated value does change from 0.5 to 1.0 but it does so instantly. There are two calls to the anime setter, once with value 0.5 then immediately a call with 1.0. If I change the property I'm animating to a standard UIView property, e.g. alpha, it works correctly.

我来自 Android 背景,所以整个 iOS 动画框架对我来说看起来像是黑魔法.除了预定义的 UIView 属性之外,还有什么方法可以为属性设置动画?

I'm coming from an Android background, so this whole iOS animation framework looks suspiciously like black magic to me. Is there any way of animating a property other than predefined UIView properties?

下面是动画视图应该是什么样子 - 它大约每 1/2 秒获得一个新值,我希望指针在这段时间内从前一个值平滑移动到下一个值.更新它的代码是:

Below is what the animated view is supposed to look like - it gets a new value about every 1/2 second and I want the pointer to move smoothly over that time from the previous value to the next. The code to update it is:

open func animate(_ progress: CGFloat) {
    //DDLogDebug("in animate: progress \(progress)")
    if(dataValid) {
        currentValue = targetValue * progress + initialValue * (1 - progress)
    }
}

并在更新后调用 draw() 将使其使用新的指针位置重绘,在 initialValuetargetValue 之间插入

And calling draw() after it's updated will make it redraw with the new pointer position, interpolating between initialValue and targetValue

简短回答:使用 CADisplayLink 每 n 帧调用一次.示例代码:

Short answer: use CADisplayLink to get called every n frames. Sample code:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    let displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
    displayLink.preferredFramesPerSecond = 50
    displayLink.add(to: .main, forMode: .defaultRunLoopMode)
    updateValues()
}

var animationComplete = false
var lastUpdateTime = CACurrentMediaTime()
func updateValues() {
    self.emaxView.animate(0);
    lastUpdateTime = CACurrentMediaTime()
    animationComplete = false
}

func animationDidUpdate(displayLink: CADisplayLink) {

    if(!animationComplete) {
        let now = CACurrentMediaTime()
        let interval = (CACurrentMediaTime() - lastUpdateTime)/animationDuration
        self.emaxView.animate(min(CGFloat(interval), 1))
        animationComplete = interval >= 1.0
    }
}

}

代码可以改进和概括,但它正在做我需要的工作.

The code could be refined and generalised but it's doing the job I needed.