iphone - cgaffinetransformrotate - Encontrar coordenadas de cuadro después de aplicar la transformación UIView(CGAffineTransform)
cgaffinetransformrotate swift 3 (6)
¿Por qué el lío y el alboroto? ¿Mantenlo simple? Donde x estaba antes de la transformación, será q rads / grados más allá, como ocurre con cada otro punto alrededor del ancla.
Lo iba a explicar todo, pero este capítulo en esta publicación lo explicó en un contexto aún más corto:
Obtener el ángulo / rotación / radian actual para una UIview?
CGFloat radians = atan2f(yourView.transform.b, yourView.transform.a);
CGFloat degrees = radians * (180 / M_PI);
CGAffineTransform
mi vista con CGAffineTransform
[view setTransform:newTransform];
Los valores del cuadro siguen siendo los mismos después de que se aplica la transformación, pero ¿cómo encuentro valores "rotados" o transformados de este cuadro?
http://www.informit.com/content/images/art_sadun_affine/elementLinks/sadun_fig02.png
Quiero las coordenadas exactas de los bordes del marco girado, es decir, los puntos a, b, c, d.
Deberías usar:
CGPoint CGPointApplyAffineTransform (
CGPoint point,
CGAffineTransform t
);
Para obtener un punto específico, use los límites y el centro de la vista, y luego aplique la transformación de la vista para obtener un nuevo punto después de la transformación. Esto es mejor que agregar código específicamente para la transformación de rotación, ya que puede admitir cualquier transformación así como también el encadenamiento.
Escribí esta clase que nos puede ayudar:
TransformedViewFrameCalculator.h
#import <Foundation/Foundation.h>
@interface TransformedViewFrameCalculator : NSObject
@property (nonatomic, strong) UIView *viewToProcess;
- (void)calculateTransformedCornersWithTranslation:(CGPoint)translation
scale:(CGFloat)scale
rotation:(CGFloat)rotation;
@property (nonatomic, readonly) CGPoint transformedTopLeftCorner;
@property (nonatomic, readonly) CGPoint transformedTopRightCorner;
@property (nonatomic, readonly) CGPoint transformedBottomLeftCorner;
@property (nonatomic, readonly) CGPoint transformedBottomRightCorner;
@end
TransformedViewFrameCalculator.m:
#import "TransformedViewFrameCalculator.h"
@interface TransformedViewFrameCalculator ()
@property (nonatomic, assign) CGRect viewToProcessNotTransformedFrame;
@property (nonatomic, assign) CGPoint viewToProcessNotTransformedCenter;
@end
@implementation TransformedViewFrameCalculator
- (void)setViewToProcess:(UIView *)viewToProcess {
_viewToProcess = viewToProcess;
CGAffineTransform t = _viewToProcess.transform;
_viewToProcess.transform = CGAffineTransformIdentity;
_viewToProcessNotTransformedFrame = _viewToProcess.frame;
_viewToProcessNotTransformedCenter = _viewToProcess.center;
_viewToProcess.transform = t;
}
- (void)calculateTransformedCornersWithTranslation:(CGPoint)translation
scale:(CGFloat)scale
rotation:(CGFloat)rotation {
double viewWidth = _viewToProcessNotTransformedFrame.size.width * scale;
double viewHeight = _viewToProcessNotTransformedFrame.size.height * scale;
CGPoint viewCenter = CGPointMake(_viewToProcessNotTransformedCenter.x + translation.x,
_viewToProcessNotTransformedCenter.y + translation.y);
_transformedTopLeftCorner = [self calculateCoordinatesForViewPoint:CGPointMake(0, 0)
fromViewCenter:viewCenter
viewWidth:viewWidth
viewHeight:viewHeight
angleOfRotation:rotation];
_transformedTopRightCorner = [self calculateCoordinatesForViewPoint:CGPointMake(0, viewHeight)
fromViewCenter:viewCenter
viewWidth:viewWidth
viewHeight:viewHeight
angleOfRotation:rotation];
_transformedBottomLeftCorner = [self calculateCoordinatesForViewPoint:CGPointMake(viewWidth, 0)
fromViewCenter:viewCenter
viewWidth:viewWidth
viewHeight:viewHeight
angleOfRotation:rotation];
_transformedBottomRightCorner = [self calculateCoordinatesForViewPoint:CGPointMake(viewWidth, viewHeight)
fromViewCenter:viewCenter
viewWidth:viewWidth
viewHeight:viewHeight
angleOfRotation:rotation];
}
- (CGPoint)calculateCoordinatesForViewPoint:(CGPoint)viewPoint
fromViewCenter:(CGPoint)viewCenter
viewWidth:(CGFloat)viewWidth
viewHeight:(CGFloat)viewHeight
angleOfRotation:(CGFloat)angleOfRotation {
CGPoint centeredViewPoint = CGPointMake(viewPoint.x - viewWidth/2.0, viewPoint.y - viewHeight/2.0);
CGPoint rotatedCenteredViewPoint = CGPointApplyAffineTransform(centeredViewPoint, CGAffineTransformMakeRotation(angleOfRotation));
CGPoint rotatedViewPoint = CGPointMake(rotatedCenteredViewPoint.x + viewCenter.x, rotatedCenteredViewPoint.y + viewCenter.y);
return rotatedViewPoint;
}
Por ejemplo, lo uso para restringir el movimiento / escala / rotación de una etiqueta dentro de una vista de contenedor de la siguiente manera:
@property (nonatomic, strong) TransformedViewFrameCalculator *transformedFrameCalculator;
...
self.transformedFrameCalculator = [TransformedViewFrameCalculator new];
self.transformedFrameCalculator.viewToProcess = someView;
...
- (BOOL)transformedView:(UIView *)view
withTranslation:(CGPoint)translation
scale:(double)scale
rotation:(double)rotation
isFullyInsideValidFrame:(CGRect)validFrame {
[self.transformedFrameCalculator calculateTransformedCornersWithTranslation:translation
scale:scale
BOOL topRightIsInsideValidFrame = CGRectContainsPoint(validFrame, self.transformedFrameCalculator.transformedTopRightCorner);
BOOL topLeftIsInsideValidFrame = CGRectContainsPoint(validFrame, self.transformedFrameCalculator.transformedTopLeftCorner);
BOOL bottomRightIsInsideValidFrame = CGRectContainsPoint(validFrame, self.transformedFrameCalculator.transformedBottomRightCorner);
BOOL bottomLeftIsInsideValidFrame = CGRectContainsPoint(validFrame, self.transformedFrameCalculator.transformedBottomLeftCorner);
return topRightIsInsideValidFrame && topLeftIsInsideValidFrame && bottomRightIsInsideValidFrame && bottomLeftIsInsideValidFrame;
}
Prueba este codigo
CGPoint localBeforeTransform = CGPointMake( view.bounds.size.width/2.0f, view.bounds.size.height/2.0f ); // lower left corner
CGPoint localAfterTransform = CGPointApplyAffineTransform(localBeforeTransform, transform);
CGPoint globalAfterTransform = CGPointMake(localAfterTransform.x + view.center.x, localAfterTransform.y + view.center.y);
Puede averiguar las coordenadas de la vista rotada utilizando la trigonometría básica. Aquí sabrás como podrás hacerlo:
El primer paso es conocer el ancho y la altura de tu vista. Divídelos por 2 y obtendrás los lados adyacentes y opuestos de tu triángulo (cian y verde respectivamente). En el ejemplo anterior, el ancho = 300 y la altura = 300. Así que adyacente al lado = 150 y al lado opuesto = 150.
Encuentra la hipotenusa (roja). Para esto usas la fórmula:
h^2 = a^2 + b^2
. Después de aplicar esta fórmula encontramos la hipotenusa = 212.13.Encontrar theta Este es el ángulo entre el lado adyacente (cian) y la hipotenusa (rojo). Para esto usas la fórmula
cos(theta) = (h^2 + a^2 - o^2)/2*h*o
. Después de aplicar esta fórmula encontramos que theta = 0.785 (RADIANES). Para convertir esto a grados, aplicamos la fórmuladegrees = radians * 180 / PI
= 45 (grados). Este es el ángulo inicial (offset) de la hipotenusa. Esto es muy importante para darse cuenta. SI LA ROTACIÓN DE LA VISTA DE SU VISTA ES CERO, LA HIPOTENJA TIENE UN ÁNGULO DE COMPENSACIÓN DE 45 (GRADOS). Vamos a necesitar theta en breve.Ahora que conocemos la hipotenusa (roja) necesitamos la rotación de ángulos. En este ejemplo usé un
UIRotationGestureRecognizer
para rotar la vista cuadrada. Esta clase tiene una propiedad de "rotación" que nos dice cuánto ha girado la vista. Este valor está en RADIANS. En el ejemplo anterior, la rotación es 0.3597 RADIANS. Para convertirlo en grados utilizamos la fórmuladegrees = radians * PI / 180
. Después de aplicar la fórmula, encontramos que el ángulo de rotación es de 20.61 grados.Finalmente podemos encontrar el ancho de desplazamiento (naranja) y la altura (púrpura). Para el ancho usamos la fórmula
width = cos(rotationAngle - theta) * hypotenuse
y para la altura usamos la fórmulaheight = sen(rotationAngle - theta)
. TENEMOS QUE SUSTITUIR A THETA (¡EN RADIANES!) DEL ÁNGULO DE ROTACIÓN (¡EN TAMBIÉN EN RADIANES!) PORQUE THETA ERA LA COMPENSACIÓN INICIAL. Míralo de esta manera: la hipotenusa tenía un ángulo de 45 (grados) cuando el ángulo de rotación era cero. Después de aplicar las fórmulas encontramos que ancho = 193.20 y altura = -87.60Finalmente, podemos agregar esos valores (ancho y alto) al centro del cuadrado para encontrar las coordenadas del punto azul.
-Ejemplo-
// Get the center point
CGPoint squareCenter = self.squareView.center;
// Get the blue point coordinates
CGPoint bluePointCoordinates = CGPointMake(squareCenter.x + width, squareCenter.y + height);
Las coordenadas del punto azul son (963.20, 272.40)
Para entender mejor las fórmulas por favor vea los siguientes enlaces:
Además, si desea jugar con el proyecto de prueba que creé (es el que está en la imagen), no dude en descargarlo desde el siguiente enlace .
ACTUALIZAR
Aquí hay un método condensado que calculará el punto de arriba a la derecha (azul) de compensación que está buscando.
/* Params:
/ viewCenter: The center point (in superView coordinates) of your view
/ width: The total width of your view
/ height: The total height of your view
/ angleOfRotation: The rotation angle of your view. Can be either DEGREES or RADIANS
/ degrees: A boolean flag indicating whether ''angleOfRotation'' is degrees
/ or radians. E.g.: If ''angleOfRotation'' is expressed in degrees
/ this parameter must be ''YES''
*/
-(CGPoint)calculateTopRightCoordinatesFromViewCenter:(CGPoint)viewCenter viewWidth:(CGFloat)viewWidth viewHeight:(CGFloat)viewHeight angleOfRotation:(CGFloat)angleOfRotation degrees:(BOOL)degrees {
CGFloat adjacent = viewWidth/2.0;
CGFloat opposite = viewHeight/2.0;
CGFloat hipotenuse = sqrtf(powf(adjacent, 2.0) + pow(opposite, 2.0));
CGFloat thetaRad = acosf((powf(hipotenuse, 2.0) + powf(adjacent, 2.0) - pow(opposite, 2.0)) / (2 * hipotenuse * adjacent));
CGFloat angleRad = 0.0;
if (degrees) {
angleRad = angleOfRotation*M_PI/180.0;
} else {
angleRad = angleOfRotation;
}
CGFloat widthOffset = cosf(angleRad - thetaRad) * hipotenuse;
CGFloat heightOffset = sinf(angleRad - thetaRad) * hipotenuse;
CGPoint offsetPoint = CGPointMake(viewCenter.x + widthOffset, viewCenter.y + heightOffset);
return offsetPoint;
}
¡Espero que esto ayude!
Una cosa que se debe tener en cuenta es que la transformación cambia el sistema de coordenadas, por lo que tendrá que poder convertir entre la vista principal y la vista secundaria (transformada). Además, las transformaciones conservan el centro del objeto transformado pero no cualquiera de las otras coordenadas. Así que necesitas calcular las cosas en términos del centro. Y hay varios ayudantes que necesitarás. (Obtuve la mayor parte del siguiente enfoque del libro de Erica Sadun, Core iOS Developer''s Cookbook).
Por lo general agrego estos como una categoría en UIView.
Para transformar las coordenadas del niño a las del padre, necesita algo como:
// helper to get pre transform frame
-(CGRect)originalFrame {
CGAffineTransform currentTransform = self.transform;
self.transform = CGAffineTransformIdentity;
CGRect originalFrame = self.frame;
self.transform = currentTransform;
return originalFrame;
}
// helper to get point offset from center
-(CGPoint)centerOffset:(CGPoint)thePoint {
return CGPointMake(thePoint.x - self.center.x, thePoint.y - self.center.y);
}
// helper to get point back relative to center
-(CGPoint)pointRelativeToCenter:(CGPoint)thePoint {
return CGPointMake(thePoint.x + self.center.x, thePoint.y + self.center.y);
}
// helper to get point relative to transformed coords
-(CGPoint)newPointInView:(CGPoint)thePoint {
// get offset from center
CGPoint offset = [self centerOffset:thePoint];
// get transformed point
CGPoint transformedPoint = CGPointApplyAffineTransform(offset, self.transform);
// make relative to center
return [self pointRelativeToCenter:transformedPoint];
}
// now get your corners
-(CGPoint)newTopLeft {
CGRect frame = [self originalFrame];
return [self newPointInView:frame.origin];
}
-(CGPoint)newTopRight {
CGRect frame = [self originalFrame];
CGPoint point = frame.origin;
point.x += frame.size.width;
return [self newPointInView:point];
}
-(CGPoint)newBottomLeft {
CGRect frame = [self originalFrame];
CGPoint point = frame.origin;
point.y += frame.size.height;
return [self newPointInView:point];
}
-(CGPoint)newBottomRight {
CGRect frame = [self originalFrame];
CGPoint point = frame.origin;
point.x += frame.size.width;
point.y += frame.size.height;
return [self newPointInView:point];
}
Swift 4
extension UIView {
/// Helper to get pre transform frame
var originalFrame: CGRect {
let currentTransform = transform
transform = .identity
let originalFrame = frame
transform = currentTransform
return originalFrame
}
/// Helper to get point offset from center
func centerOffset(_ point: CGPoint) -> CGPoint {
return CGPoint(x: point.x - center.x, y: point.y - center.y)
}
/// Helper to get point back relative to center
func pointRelativeToCenter(_ point: CGPoint) -> CGPoint {
return CGPoint(x: point.x + center.x, y: point.y + center.y)
}
/// Helper to get point relative to transformed coords
func newPointInView(_ point: CGPoint) -> CGPoint {
// get offset from center
let offset = centerOffset(point)
// get transformed point
let transformedPoint = offset.applying(transform)
// make relative to center
return pointRelativeToCenter(transformedPoint)
}
var newTopLeft: CGPoint {
return newPointInView(originalFrame.origin)
}
var newTopRight: CGPoint {
var point = originalFrame.origin
point.x += originalFrame.width
return newPointInView(point)
}
var newBottomLeft: CGPoint {
var point = originalFrame.origin
point.y += originalFrame.height
return newPointInView(point)
}
var newBottomRight: CGPoint {
var point = originalFrame.origin
point.x += originalFrame.width
point.y += originalFrame.height
return newPointInView(point)
}
}