Trouver les coordonnées du Cadre après L'application de la transformation UIView (Gcaffinetransform))
je tourne ma vue avec CGAffineTransform
[view setTransform:newTransform];
les valeurs du cadre restent les mêmes après l'application de transform, mais comment puis-je trouver les valeurs "tournées" ou transformées de ce cadre?
http://www.informit.com/content/images/art_sadun_affine/elementLinks/sadun_fig02.png
je veux les coordonnées exactes des bords tournés du cadre, c'est-à-dire les points a, b, C, D.
6 réponses
une chose à garder à l'esprit est que la transformation change le système de coordonnées, vous aurez donc besoin d'être en mesure de convertir entre la 'vue' du parent et la vue de l'enfant (transformé). Aussi, transforme préserver le centre de l'objet transformé, mais les autres coordonnées. Donc vous devez calculer les choses en termes de Centre. Et il y a plusieurs aides dont vous aurez besoin. (J'ai obtenu la plupart de l'approche suivante du Livre de base iOS de Erica Sadun Cookbook).
je les ajoute habituellement comme une catégorie sur uivi.
pour transformer les coordonnées de l'enfant en celles du parent, vous avez besoin de quelque chose comme:
// 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)
}
}
vous pouvez trouver les coordonnées de la vue tournée en utilisant la trigonométrie de base. Voici comment vous pouvez le faire:
-
la première étape consiste à connaître la largeur et la hauteur de votre vue. Divisez - les par 2 et vous obtenez les côtés adjacents et opposés de votre triangle (cyan et vert respectivement). Dans l'exemple ci-dessus, largeur = 300 et hauteur = 300. Donc adjacentSide = 150 et oppositeSice = 150.
-
Trouver l'hypoténuse (rouge). Pour cela, vous utilisez la formule:
h^2 = a^2 + b^2
. Après application de cette formule nous trouvons l'hypoténuse = 212.13 -
trouver theta. C'est l'angle entre la adjacentSide (cyan) et l'hypoténuse (rouge). Pour cela, vous utilisez la formule
cos(theta) = (h^2 + a^2 - o^2)/2*h*o
. Après application de cette formule nous trouvons que theta = 0.785 (RADIANS). Pour convertir cela en degrés nous appliquons la formuledegrees = radians * 180 / PI
= 45 (degrés). C'est l'angle initial (offset) de l'hypoténuse. Ceci est très important à comprendre. SI LA ROTATION DE LA VUE DE VOTRE VUE EST ZÉRO L'HYPOTÉNUSE A UN ANGLE DE DÉPORT DE 45 (DEGRÉS). Nous allons bientôt avoir besoin de thêta. -
maintenant que nous connaissons l'hypoténuse (rouge) nous avons besoin de l'anneau de rotation. Dans cet exemple, j'ai utilisé un
UIRotationGestureRecognizer
pour tourner la vue carrée. Cette classe a une propriété "rotation" qui nous indique combien le point de vue a tourné. Cette valeur est en RADIANS. Dans l'exemple ci-dessus, la rotation est de 0,3597 RADIANS. Pour le convertir en degrés, nous utilisons la formuledegrees = radians * PI / 180
. Après avoir appliqué la formule, nous trouvons que l'angle de rotation est de 20,61 degrés. -
nous pouvons enfin trouver la largeur offset (orange) et la hauteur (pourpre). Pour la largeur, nous utilisons la formule
width = cos(rotationAngle - theta) * hypotenuse
et pour la taille, nous utilisons la formuleheight = sen(rotationAngle - theta)
. NOUS DEVONS SOUSTRAIRE THÊTA (EN RADIANS!) À PARTIR DE LA ANGLE DE ROTATION (EN RADIANS AUSSI!) PARCE QUE THETA ÉTAIT L'OFFSET INITIAL. Vue de cette façon: l'hypoténuse a un angle de 45 degrés) lorsque l'angle de rotation était de zéro. Après avoir appliqué les formules, nous trouvons que la largeur = 193,20 et la hauteur = -87,60 -
enfin, nous pouvons ajouter ces valeurs (largeur et hauteur) au centre du carré pour trouver les coordonnées du point bleu.
- exemple -
// Get the center point
CGPoint squareCenter = self.squareView.center;
// Get the blue point coordinates
CGPoint bluePointCoordinates = CGPointMake(squareCenter.x + width, squareCenter.y + height);
les coordonnées des points bleus sont (963.20, 272.40)
pour mieux comprendre les formules s'il vous plaît voir les liens suivants:
aussi, si vous voulez jouer avec le projet test que j'ai créé (c'est celui de l'image) s'il vous plaît n'hésitez pas à télécharger à partir de le lien suivant .
mise à JOUR
Voici une méthode condensée qui va calculer le décalage du point en haut à droite (bleu) que vous recherchez.
/* 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;
}
Espérons que cette aide!
vous devez utiliser:
CGPoint CGPointApplyAffineTransform (
CGPoint point,
CGAffineTransform t
);
pour obtenir un point spécifique, utilisez les limites et le centre de la vue, puis appliquez la transformation de la vue pour obtenir un nouveau point après transformation. C'est mieux que d'ajouter du code spécifiquement pour la transformation de rotation, car il peut supporter n'importe quelle transformation ainsi que l'enchaînement.
essayez ce code
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);
pourquoi tout ce bazar? Garder ça simple? Là où x était avant la transformation, ce sera q rads/degrés plus loin comme tous les autres points autour de l'ancre.
allait tout expliquer, mais ce chap dans ce post l'a expliqué dans un contexte encore plus court:
obtenir l'angle de courant / rotation / radian pour un uivi?
CGFloat radians = atan2f(yourView.transform.b, yourView.transform.a);
CGFloat degrees = radians * (180 / M_PI);
j'ai écrit cette classe qui peut nous aider:
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;
}
par exemple, je l'utilise pour restreindre le mouvement/échelle / rotation d'un autocollant à l'intérieur d'un conteneur vue de la manière suivante:
@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;
}