ios - Escala y traducción de CGAffineTransform-Saltar antes de la animación
xcode scale (5)
En lugar de CGAffineTransformMakeScale () y CGAffineTransformMakeTranslation (), que crean una transformación basada en CGAffineTransformIdentity (básicamente ninguna transformación), desea escalar y traducir en función de la transformación actual de la vista utilizando CGAffineTransformScale () y CGAffineTransformTranslate (), que comienza con la existente transformar.
Estoy luchando con un problema relacionado con la escala y la traducción de CGAffineTransform, donde cuando configuro una transformación en un bloque de animación en una vista que ya tiene una transformación, la vista salta un poco antes de animar.
Ejemplo:
// 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
})
}
Ahora, cuando se llama buttonPressed (), la vista salta a la parte superior izquierda unos 10 píxeles antes de comenzar a animar. Solo presencié este problema con una transformación concat, usar solo una transformación de traducción funciona bien.
Edit: Ya que he investigado mucho sobre el tema, creo que debería mencionar que este problema aparece independientemente de si el diseño automático está activado o no.
La fuente del problema es la falta de información en perspectiva para la transformación.
Puede agregar información de perspectiva modificando la propiedad m34
de su transformación 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
Me encontré con el mismo problema, pero no pude encontrar la fuente exacta del problema. El salto parece aparecer solo en condiciones muy específicas: si la vista se anima desde una transformación t1
a una transformación t2
y ambas transformaciones son una combinación de una escala y una traducción (ese es exactamente su caso). Dada la siguiente solución, que no tiene sentido para mí, asumo que es un error en Core Animation.
Primero, intenté usar CATransform3D
lugar de CGAffineTransform
.
Código antiguo:
var transform = CGAffineTransformIdentity
transform = CGAffineTransformScale(transform, 1.1, 1.1)
transform = CGAffineTransformTranslate(transform, 10, 10)
view.layer.setAffineTransform(transform)
Nuevo código:
var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform
El nuevo código debe ser equivalente al anterior (el cuarto parámetro se establece en 1.0
o 0
para que no haya escalado / traducción en la dirección z
), y de hecho muestra el mismo salto. Sin embargo, aquí viene la magia negra: en la transformación de escala, cambia el parámetro z
a algo diferente de 1.0
, como esto:
transform = CATransform3DScale(transform, 1.1, 1.1, 1.01)
Este parámetro no debería tener ningún efecto, pero ahora el salto se ha ido.
🎩✨
Parece que la animación de Apple UIView error interno. Cuando Apple interpola los cambios de CGAffineTransform
entre dos valores para crear la animación, debe realizar los siguientes pasos:
- Extraer traslación, escala y rotación.
- Interpolar los valores extraídos de principio a fin.
- Ensamble
CGAffineTransform
para cada paso de interpolación
El montaje debe ser en el siguiente orden:
- Traducción
- Escalada
- Rotación
Pero parece que Apple hace la traducción después de la escala y la rotación. Este error debe ser arreglado por Apple.
No sé por qué, pero este código puede funcionar.
actualizar:
Combino con éxito escala, traslación y rotación, desde cualquier estado de transformación a cualquier nuevo estado de transformación.
Creo que la transformación se reinterpreta al comienzo de la animación.
el ancla de la transformación de inicio se considera en la nueva transformación, y luego la convertimos a la transformación antigua.
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
}
}
También intento la solución zScale, parece que también funciona si se establece zScale non-1 en la primera transformación o en cada transformación
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
}