web-dev-qa-db-fra.com

Rechercher les coordonnées du cadre après l'application de la transformation UIView (CGAffineTransform)

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.

29
Vad

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)
    }
}
44
Bladebunny

Vous pouvez trouver les coordonnées de la vue pivotée en utilisant la trigonométrie de base. Voici comment vous pouvez le faire:

enter image description here

  1. 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.

  2. 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

  3. 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.

  4. 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.

  5. 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

  6. 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!

16
LuisCien

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.

4
Leo Natan

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);
1
Adrian Sluyters

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);
1
MarekR

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;
}
0
kanobius