2017-01-09 9 views
0

Core Graphics呼び出しを使用してコンテンツを描画するカスタムUIViewがあります。すべてうまくいきましたが、ディスプレイに影響を与える値の変化をアニメーション化したいと考えています。iOSでカスタムプロパティをアニメーション化する方法

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

そして私はViewControllerをからアニメーションを開始している:私は私のカスタムUViewでこれを達成するためのカスタムプロパティを持っている

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に変更しましたが、即座に変更されます。アニメセッターへの呼び出しは2回あり、1回は値0.5で、その後は1.0で直ちに呼び出されます。プロパティを変更すると、標準のUIViewプロパティにアニメートしています。アルファ、それは正しく動作します。

私はAndroidの背景から来ているので、このiOSのアニメーションフレームワーク全体は疑いの余地なく黒い魔法のように見えます。定義済みのUIViewプロパティ以外のプロパティをアニメーション化する方法はありますか?

アニメーション表示は次のようになっています:1/2秒ごとに新しい値が得られ、その間にポインタが前の値から次の値にスムーズに移動します。それを更新するためのコードは次のとおりです。

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

そしてそれが更新されます後draw()を呼び出すには、あなたが呼び出す必要がありますenter image description here

initialValue間と targetValue

+0

何がこのプロパティの変更を達成しようとしていますか?プロパティがdraw(in:rect)で実行される描画の一部の外観を変更した場合、そのようにアニメートすることはできません。 –

+0

@twiz_そうだね。どのようにそれを行うにはどのような提案? – Clyde

+0

'CAAnimation'を使って、カスタムドローイングの図面を修正してみましょう!私は 'UIView.animate'ウェイがカスタムドロー矩形描画で動作するかどうか分かりません! – Rikh

答えて

-1

を補間する、それが新しいポインタ位置で再描画になりますlayoufIfNeeded()あなたのgauge.animate(newValue)関数で自動レイアウト制約を変更した場合、setNeedsDisplay()の代わりに。

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

+1

で質問を更新@twiz_役立つ – Clyde

1

短い答え:すべてのnフレームと呼ばれる取得するCADisplayLinkを使用しています。サンプルコード:

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 
    } 
} 

}

コードは、洗練と一般化が、それは私が必要な仕事をやっていることができました。

0

CoreGraphicsで完全に描画されている場合は、少し計算したい場合は、これをアニメーション化するかなり簡単な方法があります。幸いなことに、正確に回転するラジアンの数を示すスケールがあるので、数学は最小であり、三角法は関係ありません。この利点は、バックグラウンド全体またはポインタを再描画する必要がないことです。角度や物事を得るのはちょっと難しいかもしれませんが、次のことがうまくいかない場合は助けてください。

ビューの背景を描画(通常はrect)で描画します。 CALayerに入れるべきポインタ。ポインターの描画コードを中心の濃いグレーの円をUIImageを返す別のメソッドに移動するだけで十分です。レイヤーは(レイアウトのサブビューでは)ビューのフレームのサイズになり、アンカーポイントは(0.5、0.5)に設定する必要があります。これは実際にはデフォルトです。次に、アニメーションメソッドはレイヤの変形を必要なだけ回転させます。ここで私はそれをやるでしょう。アニメやアニメーションはあまりにもあいまいなので、メソッドと変数の名前を変更します。

レイヤプロパティは暗黙的に0の持続時間でアニメーション化されるためです。あなたはアニメーションの方法を呼び出すことなしに逃げることができるかもしれません。 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がトリガされます。私はこれを忘れてしまった。これを回避する最も簡単な方法は、SpeedDialのサブビューであるUIViewにポインタレイヤを配置することです。コードを更新しました。がんばろう!たぶん、これは過剰ですが、ビュー、背景、およびすべてのレンダリング全体をアニメートするよりも少し再利用可能です。

+0

詳細な回答ありがとうございます。はい、それはすべてCore Graphicsで行われ、CGContextはスケーリングされて変換され、針を中心とした100x100のボックスに描画されます。数値のアニメーション化はどうですか?いずれにしても、各アニメーションサイクルを再描画する必要があります。また、同じUIViewに複数のゲージがあり、同時にすべてが更新されるため、それらをまとめてアニメーション化するのが理にかなっています。複数のサブビューを追加するのは面倒です。現在、ゲージを描画するクラスが1つあります。非常に軽量ですが、ゲージごとに1つのパラメータ化されたインスタンスがあります。 – Clyde

+0

アニメーションが必要な他の要素もありますが、これは標準のUIViewやレイヤプロパティでは実行できません。長さと色が変わるバー。 – Clyde

+0

汗がかからない。長さと色が変わるバーは、CAShapeLayersとして最も効果的です。 RPMテキストの変更 - スピードダイヤルにUILabelサブビューを追加し、それに応じて配置し、rpm変数のdidSet節の値を変更します。 –

関連する問題