web-dev-qa-db-fra.com

CGAffineTransform balance et translation - saute avant l'animation

Je suis aux prises avec un problème relatif à l'échelle et à la traduction de CGAffineTransform, où lorsque je définis une transformation dans un bloc d'animation sur une vue comportant déjà une transformation, la vue saute un peu avant l'animation.

Exemple:

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

}

Désormais, lorsque buttonPressed () est appelée, la vue passe en haut à gauche à environ 10 pixels avant de commencer à animer. J'ai seulement été témoin de ce problème avec une transformation de concat, utiliser uniquement une transformation de traduction fonctionne bien.

Edit: Depuis que j'ai fait beaucoup de recherches sur le sujet, je pense que je devrais mentionner que ce problème apparaît que la mise en page automatique soit activée ou non

20
matteok

J'ai rencontré le même problème, mais je n'ai pas pu trouver la source exacte du problème. Le saut semble apparaître uniquement dans des conditions très spécifiques: Si la vue s'anime d'une transformation t1 à une transformation t2 et que les deux transformations associent une échelle et une traduction (c'est exactement ce que vous faites). Compte tenu de la solution de contournement suivante, ce qui n’a aucun sens pour moi, je suppose que c’est un bogue dans Core Animation.

J'ai d'abord essayé d'utiliser CATransform3D au lieu de CGAffineTransform.

Ancien code:

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

Nouveau code:

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

Le nouveau code doit être équivalent à l'ancien (le quatrième paramètre est défini sur 1.0 ou 0 de sorte qu'il n'y ait pas de redimensionnement/de traduction dans la direction z), et montre en fait le même saut. Cependant, voici la magie noire: dans la transformation d'échelle, remplacez le paramètre z par un élément différent de 1.0, comme ceci:

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

Ce paramètre ne devrait avoir aucun effet, mais maintenant le saut est parti. 

????

38
Theo

Ressemble au bogue interne de l'animation Apple UIView. Lorsque Apple interpole CGAffineTransform entre deux valeurs pour créer une animation, procédez comme suit:

  • Extraire la traduction, l'échelle et la rotation
  • Interpole les valeurs extraites du début à la fin
  • Assemblez CGAffineTransform pour chaque étape d'interpolation

L'assemblage devrait être dans l'ordre suivant:

  • Traduction
  • Mise à l'échelle
  • Rotation

Mais on dirait que Apple fait la traduction après la mise à l'échelle et la rotation. Ce bogue devrait être corrigé par Apple. 

4
k06a

Je ne sais pas pourquoi, mais ce code peut fonctionner

mettre à jour: 

Je réussis à combiner la mise à l'échelle, la traduction et la rotation ensemble, d'un état de transformation à un nouvel état de transformation.

Je pense que la transformation est réinterprétée au début de l'animation.

l'ancre de la transformation de début est prise en compte dans la nouvelle transformation, puis nous la convertissons en ancienne transformation.

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

J'essaie également la solution zScale. Il semble également fonctionner si vous définissez zScale non-1 à la première transformation ou à chaque transformation.

    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

La source du problème est le manque d'informations de perspective sur la transformation.

Vous pouvez ajouter des informations de perspective en modifiant la propriété m34 de votre transformation 3D.

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

Au lieu de CGAffineTransformMakeScale () et CGAffineTransformMakeTranslation (), qui créent une transformation à partir de CGAffineTransformIdentity (essentiellement aucune transformation), vous souhaitez redimensionner et traduire en fonction de la transformation actuelle de la vue à l'aide de CGAffineTransformScale () et CGAffineTransformlate (), qui démarre avec la transformation en cours. transformer.

0
Dave Batton