如何在iOS

时间:2017-01-09 04:06:14

标签: ios swift uiviewanimation

我有一个自定义UIView,它使用Core Graphics调用来绘制其内容。一切运作良好,但现在我想动画一个影响显示的价值变化。我有一个自定义属性来实现我的自定义UView:

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

我已经从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,它工作正常。

我来自Android背景,因此整个iOS动画框架看起来像我的黑魔法。除了预定义的UIView属性之外,还有什么方法可以动画属性吗?

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

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

在更新后调用draw()会使其重新绘制新的指针位置,在initialValuetargetValue

之间进行插值

enter image description here

3 个答案:

答案 0 :(得分:1)

简答:使用AppDelegate每n帧调用一次。示例代码:

CADisplayLink

}

代码可以改进和推广,但它正在完成我需要的工作。

答案 1 :(得分:0)

如果完全使用CoreGraphics绘制,如果你想做一些数学运算,有一种非常简单的动画制作方法。幸运的是,你有一个刻度可以告诉你完全旋转的弧度数,所以数学是最小的,不涉及三角学。这样做的好处是你不必重绘整个背景,甚至指针。如果以下方法不起作用,我可以提供帮助。

通常在绘图中绘制视图的背景(在rect中)。你应该把它放到CALayer中的指针。您几乎可以只将指针的绘制代码移动到一个返回UIImage的单独方法中,包括中心深灰色圆圈。图层的大小将调整到视图的框架(在布局子视图中),并且锚点必须设置为(0.5,0.5),这实际上是默认值,因此您应该可以将该行留出。然后,您的动画方法只是根据您的需要更改图层的变换以进行旋转。这是我将如何做到的。我要更改方法和变量名称,因为动画和动画只是有点太模糊了。

因为图层属性隐含地以0.25的持续时间进行动画制作,所以即使不调用动画方法也可以离开。自从我使用CoreAnimation以来已经有一段时间了,所以显然要测试它。

这里的优点是你只需将表盘的RPM设置为你想要的,它就会旋转到那个速度。没有人会读你的代码,就像WTF是_anime! :)我已经包含了init方法来提醒你改变图层的内容比例(或者它呈现的是低质量),显然你的init中可能还有其他东西。

class SpeedDial: UIView {

    var pointer: CALayer!
    var pointerView: UIView!

    var rpm: CGFloat = 0 {
        didSet {
            pointer.setAffineTransform(rpm == 0 ? .identity : CGAffineTransform(rotationAngle: rpm/25 * .pi))
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        pointer = CALayer()
        pointer.contentsScale = UIScreen.main.scale

        pointerView = UIView()
        addSubview(pointerView)
        pointerView.layer.addSublayer(pointer)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        pointer = CALayer()
        pointer.contentsScale = UIScreen.main.scale

        pointerView = UIView()
        addSubview(pointerView)
        pointerView.layer.addSublayer(pointer)
    }

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }

        context.saveGState()

        //draw background with values
        //but not the pointer or centre circle

        context.restoreGState()
    }


    override func layoutSubviews() {
        super.layoutSubviews()

        pointerView.frame = bounds

        pointer.frame = bounds
        pointer.anchorPoint = CGPoint(x: 0.5, y: 0.5)

        pointer.contents = drawPointer(in: bounds)?.cgImage
    }


    func drawPointer(in rect: CGRect) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }

        context.saveGState()

        // draw the pointer Image. Make sure to draw it pointing at zero. ie at 8 o'clock
        // I'm not sure what your drawing code looks like, but if the pointer is pointing
        // vertically(at 12 o'clock), you can get it pointing at zero by rotating the actual draw context like so:
        // perform this context rotation before actually drawing the pointer
        context.translateBy(x: rect.width/2, y: rect.height/2)
        context.rotate(by: -17.5/25 * .pi) // the angle judging by the dial - remember .pi is 180 degrees
        context.translateBy(x: -rect.width/2, y: -rect.height/2)



        context.restoreGState()
        let pointerImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()
        return pointerImage

    }

}

指针的标识转换使其指向0 RPM,因此每次将RPM提升到所需的值时,它都会向上旋转到该值。

编辑:测试它,它的工作原理。除了我犯了几个错误 - 你不需要更改图层位置,我相应地更新了代码。此外,更改图层的变换会触发直接父级中的layoutSubviews。我忘了这件事。最简单的方法是将指针层放入一个UIView,它是SpeedDial的子视图。我已经更新了代码。祝好运!也许这有点过分,但它比动画整个视图,背景和所有渲染更具可重用性。

答案 2 :(得分:-1)

如果修改layoufIfNeeded()功能中的任何自动布局限制,则需要致电setNeedsDisplay()而不是gauge.animate(newValue)

https://stackoverflow.com/a/12664093/255549