web-dev-qa-db-ja.com

CGAffineTransformのスケールと変換-アニメーションの前にジャンプ

CGAffineTransformのスケールと変換に関する問題に取り組んでいます。変換が既にあるビューのアニメーションブロックで変換を設定すると、ビューがアニメーションする前に少しジャンプします。

例:

// somewhere in view did load or during initialization
var view = UIView()
view.frame = CGRectMake(0,0,100,100)
var scale = CGAffineTransformMakeScale(0.8,0.8)
var translation = CGAffineTransformMakeTranslation(100,100)
var concat = CGAffineTransformConcat(translation, scale)
view.transform = transform

// called sometime later
func buttonPressed() {
    var secondScale = CGAffineTransformMakeScale(0.6,0.6)
    var secondTranslation = CGAffineTransformMakeTranslation(150,300)
    var secondConcat = CGAffineTransformConcat(secondTranslation, secondScale)
    UIView.animateWithDuration(0.5, animations: { () -> Void in 
         view.transform = secondConcat
    })

}

ここで、buttonPressed()が呼び出されると、ビューはアニメーション化を開始する前に約10ピクセル左上にジャンプします。私はこの問題をconcat変換でのみ見ましたが、translation変換のみを使用するとうまくいきます。

編集:私はこの問題について多くの調査を行ったので、自動レイアウトがオンになっているかどうかにかかわらず、この問題が発生することを言及する必要があると思います

23
matteok

私は同じ問題に出くわしましたが、問題の正確な原因を見つけることができませんでした。ジャンプは非常に特定の条件でのみ表示されるようです:ビューが変換t1から変換t2にアニメーション化し、両方の変換がスケールと変換の組み合わせである場合(まさにあなたのケースです)。私には意味のない次の回避策を考えると、Core Animationのバグだと思います。

まず、CGAffineTransformではなくCATransform3Dを使用してみました。

古いコード:

var transform = CGAffineTransformIdentity
transform = CGAffineTransformScale(transform, 1.1, 1.1)
transform = CGAffineTransformTranslate(transform, 10, 10)
view.layer.setAffineTransform(transform)

新しいコード:

var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform

新しいコードは古いコードと同等である必要があり(4番目のパラメーターは1.0または0に設定されているため、z方向のスケーリング/変換がないため)、実際には同じジャンプを示します。ただし、ここでブラックマジックが発生します。スケール変換で、zパラメータを1.0とは異なるものに次のように変更します。

transform = CATransform3DScale(transform, 1.1, 1.1, 1.01)

このパラメーターは効果がないはずですが、ジャンプはなくなりました。

????✨

46
Theo

Apple UIViewアニメーションの内部バグのように見えます。Appleが2つの値の間のCGAffineTransformの変更を補間してアニメーションを作成する場合、次の手順を実行する必要があります。

  • 平行移動、スケール、回転を抽出する
  • 抽出された値を最初から最後まで補間する
  • 補間ステップごとにCGAffineTransformを組み立てます

組み立ては次の順序で行う必要があります。

  • 翻訳
  • スケーリング
  • 回転

しかし、Appleのように見えます。スケーリングと回転後に変換を行います。このバグはAppleによって修正されるはずです。

4
k06a

理由はわかりませんが、このコードは機能します

更新:

あらゆる変換状態から新しい変換状態へのスケーリング、変換、回転をうまく組み合わせています。

アニメーションの開始時に変換が再解釈されると思います。

start変換のアンカーは新しい変換で考慮され、それから古い変換に変換します。

self.v  = UIView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
self.v?.backgroundColor = .blue
self.view.addSubview(v!)

func buttonPressed() {
    let view = self.v!

    let m1 = view.transform
    let tempScale = CGFloat(arc4random()%10)/10 + 1.0
    let tempRotae:CGFloat = 1
    let m2 = m1.translatedBy(x: CGFloat(arc4random()%30), y: CGFloat(arc4random()%30)).scaledBy(x: tempScale, y: tempScale).rotated(by:tempRotae)
    self.animationViewToNewTransform(view: view, newTranform: m2)
}    


func animationViewToNewTransform(view: UIView, newTranform: CGAffineTransform) {
    // 1. pointInView.apply(view.transform) is not correct point.
    // the real matrix is mAnchorToOrigin.inverted().concatenating(m1).concatenating(mAnchorToOrigin)
    // 2. animation begin trasform is relative to final transform in final transform coordinate

    // anchor and mAnchor
    let normalizedAnchor0 = view.layer.anchorPoint
    let anchor0 = CGPoint(x: normalizedAnchor0.x * view.bounds.width, y: normalizedAnchor0.y * view.bounds.height)
    let mAnchor0 = CGAffineTransform.identity.translatedBy(x: anchor0.x, y: anchor0.y)

    // 0->1->2
    //let Origin = CGPoint(x: 0, y: 0)
    //let m0 = CGAffineTransform.identity
    let m1 = view.transform
    let m2 = newTranform

    // rotate and scale relative to anchor, not to Origin
    let matrix1 = mAnchor0.inverted().concatenating(m1).concatenating(mAnchor0)
    let matrix2 = mAnchor0.inverted().concatenating(m2).concatenating(mAnchor0)
    let anchor1 = anchor0.applying(matrix1)
    let mAnchor1 = CGAffineTransform.identity.translatedBy(x: anchor1.x, y: anchor1.y)
    let anchor2 = anchor0.applying(matrix2)
    let txty2 = CGPoint(x: anchor2.x - anchor0.x, y: anchor2.y - anchor0.y)
    let txty2plusAnchor2 = CGPoint(x: txty2.x + anchor2.x, y: txty2.y + anchor2.y)
    let anchor1InM2System = anchor1.applying(matrix2.inverted()).applying(mAnchor0.inverted())
    let txty2ToM0System = txty2plusAnchor2.applying(matrix2.inverted()).applying(mAnchor0.inverted())
    let txty2ToM1System = txty2ToM0System.applying(mAnchor0).applying(matrix1).applying(mAnchor1.inverted())

    var m1New = m1
    m1New.tx = txty2ToM1System.x + anchor1InM2System.x
    m1New.ty = txty2ToM1System.y + anchor1InM2System.y

    view.transform = m1New
    UIView.animate(withDuration: 1.4) {
        view.transform = m2
    }
}

私もzScaleソリューションを試します。最初の変換またはすべての変換でzScaleを1以外に設定しても機能するようです

    let oldTransform = view.layer.transform
    let tempScale = CGFloat(arc4random()%10)/10 + 1.0
    var newTransform = CATransform3DScale(oldTransform, tempScale, tempScale, 1.01)
    newTransform = CATransform3DTranslate(newTransform, CGFloat(arc4random()%30), CGFloat(arc4random()%30), 0)
    newTransform = CATransform3DRotate(newTransform, 1, 0, 0, 1)

    UIView.animate(withDuration: 1.4) {
        view.layer.transform = newTransform
    }
0
lbsweek

問題の原因は、変換に対する視点情報の欠如です。

3D変換のm34プロパティを変更するパースペクティブ情報を追加できます

var transform = CATransform3DIdentity
transform.m34 = 1.0 / 200 //your own perspective value here
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform
0
Snit

CGAffineTransformIdentity(基本的には変換なし)に基づいて変換を作成するCGAffineTransformMakeScale()およびCGAffineTransformMakeTranslation()の代わりに、既存のCGAffineTransformScale()およびCGAffineTransformTranslate()を使用して、ビューの現在の変換に基づいてスケーリングおよび変換する必要があります。変身。

0
Dave Batton