Je tourne ma vue avec CGAffineTransform
[view setTransform:newTransform];
Les valeurs d'image restent les mêmes après l'application de la transformation, mais comment puis-je trouver les valeurs "pivotées" ou transformées de cette image?
http://www.informit.com/content/images/art_sadun_affine/elementLinks/sadun_fig02.png
Je veux les coordonnées exactes des bords du cadre pivoté, c’est-à-dire les points a, b, c, d.
Une chose à garder à l'esprit est que la transformation modifie le système de coordonnées. Vous devez donc pouvoir effectuer la conversion entre la vue parent et la vue enfant (transformée). De plus, les transformations préservent le centre de l'objet transformé, mais pas les autres coordonnées. Donc, vous devez calculer les choses en termes de centre. Et vous aurez besoin de plusieurs aides. (Le livre d'Erica Sadun, Core iOS Developer's Cookbook, contient l'essentiel de l'approche suivante).
J'ajoute généralement ceux-ci en tant que catégorie sur UIView.
Afin de 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 pivoté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 (respectivement cyan et vert). Dans l'exemple ci-dessus, width = 300 et height = 300. So adjacentSide = 150 et reverseSice = 150.
Trouvez 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
Trouvez thêta. C'est l'angle entre le côté adjacent (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 formule degrees = radians * 180 / PI
= 45 (degrés). C'est l'angle initial (offset) de l'hypoténuse. C'est très important à réaliser. SI LA VUE ROTATION DE VOTRE VUE IS ZÉRO, L'HYPOTENUSE A UN ANGLE DÉCALÉ DE 45 (DEGRÉS). Nous allons avoir besoin de thêta sous peu.
Maintenant que nous connaissons l'hypoténuse (rouge), nous avons besoin de l'angle de rotation. Dans cet exemple, j'ai utilisé une variable UIRotationGestureRecognizer
pour faire pivoter la vue carrée. Cette classe a une propriété "rotation" qui nous dit combien la 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 formule degrees = radians * PI / 180
. Après application de la formule, l'angle de rotation est de 20,61 degrés.
On peut enfin trouver le décalage largeur (orange) et hauteur (violet). Pour la largeur, nous utilisons la formule width = cos(rotationAngle - theta) * hypotenuse
et pour la hauteur, la formule height = sen(rotationAngle - theta)
. WE DOIVENT SOUSTRACTER LE THÉTA (DANS LES RADIENS!) DE L'ANGLE DE ROTATION (DANS LES RADIENS EN PLUS!) PARCE QUE THETA ÉTAIT LE DÉCALAGE INITIAL. Voyez-le de cette façon: l'hypoténuse avait un angle de 45 (degrés) lorsque l'angle de rotation était égal à zéro. Après avoir appliqué les formules, nous trouvons que width = 193.20 et height = -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 du point bleu sont (963.20, 272.40)
Pour mieux comprendre les formules, veuillez consulter les liens suivants:
Aussi, si vous voulez jouer avec le projet de test que j'ai créé (c'est celui de l'image), n'hésitez pas à le télécharger à partir de le lien suivant .
METTRE À JOUR
Voici une méthode condensée qui calculera le décalage 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;
}
J'espère que cela t'aides!
Tu devrais 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 la transformation. C'est mieux que d'ajouter du code spécifiquement pour la transformation de rotation, car il peut prendre en charge toute transformation ainsi que le chaînage.
Pourquoi tout ce bazar? Rester simple? Où x était avant la transformation, ce sera q rads/degrés plus loin, comme tous les autres points autour de l'ancre.
j'allais tout expliquer, mais ce gars de ce post l'a expliqué dans un contexte encore plus court:
Obtenir l'angle/la rotation/le radian actuels pour une UIview?
CGFloat radians = atan2f(yourView.transform.b, yourView.transform.a);
CGFloat degrees = radians * (180 / M_PI);
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);
J'ai écrit ce cours 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 déplacement/l'échelle/la rotation d'un autocollant à l'intérieur d'une vue de conteneur 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;
}