Find Frame Coordinates After UIView Transform is Applied (CGAffineTransform)

空扰寡人 提交于 2020-01-27 04:50:13

问题


I rotate my view with CGAffineTransform

[view setTransform:newTransform];

The frame values remain the same after transform is applied but how do I find "rotated" or transformed values of this frame?


(source: informit.com)

I want the exact coordinates of rotated frame edges, that is a, b, c, d points.


回答1:


One thing to keep in mind is that the transform changes the coordinate system, so you will need to be able to convert between the parent 'view' and the child (transformed) view. Also, transforms preserve the center of the transformed object but not any of the other coordinates. So you need to calculate things in terms of the center. And there are several helpers you will need. (I got most of the following approach from Erica Sadun's book Core iOS Developer's Cookbook).

I usually add these as a category on UIView.

In order to transform the child's coordinates to those of the parent you need something like:

// 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)
    }
}



回答2:


You can find out the coordinates of the rotated view by using basic trigonometry. Here is how you can do it:

  1. The first step is to know your view's width and height. Divide them by 2 and you get your triangle's adjacent and opposite sides (cyan and green respectively). In the example above width = 300 and height = 300. So adjacentSide = 150 and oppositeSice = 150.

  2. Find the hypotenuse (red). For this you use the formula: h^2 = a^2 + b^2. After applying this formula we find the hypotenuse = 212.13

  3. Find theta. This is the angle between the adjacentSide (cyan) and the hypotenuse (red). For this you use the formula cos(theta) = (h^2 + a^2 - o^2)/2*h*o. After applying this formula we find that theta = 0.785 (RADIANS). To convert this to degrees we apply the formula degrees = radians * 180 / PI = 45 (degrees). This is the initial (offset) angle of the hypotenuse. This is very important to realize. IF THE VIEW'S ROTATION OF YOUR VIEW IS ZERO THE HYPOTENUSE HAS AN OFFSET ANGLE OF 45(DEGREES). We're going to need theta shortly.

  4. Now that we know the hypotenuse (red) we need the rotationAngle. In this example I used a UIRotationGestureRecognizer to rotate the square view. This class has a "rotation" property which tells us how much the view has rotated. This value is in RADIANS. In the example above the rotation is 0.3597 RADIANS. To convert it to degrees we use the formula degrees = radians * PI / 180. After applying the formula we find the rotation angle to be 20.61 degrees.

  5. We can finally find the offset width (orange) and height (purple). For width we use the formula width = cos(rotationAngle - theta) * hypotenuse and for height we use the formula height = sen(rotationAngle - theta). WE HAVE TO SUBTRACT THETA (IN RADIANS!) FROM THE ROTATION ANGLE (IN RADIANS TOO!) BECAUSE THETA WAS THE INITIAL OFFSET. View it this way: the hypotenuse had an angle of 45(degrees) when the rotation angle was zero. After applying the formulas we find that width = 193.20 and height = -87.60

  6. Finally, we can add those values (width and height) to the center of the square to find the coordinates of the blue point.

-Example-

// Get the center point
CGPoint squareCenter = self.squareView.center;

// Get the blue point coordinates
CGPoint bluePointCoordinates = CGPointMake(squareCenter.x + width, squareCenter.y + height);

The blue point coordinates are (963.20, 272.40)

To better understand the formulas please see the following links:

  • Trigonometry 1
  • Trigonometry 2

Also, if you want to play around with the test project I created (it's the one in the image) please feel free to download it from the following link.

UPDATE

Here is a condensed method that will calculate the offset top-right point (blue) you're looking for.

/* 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;
}

Hope this helps!




回答3:


You should use:

CGPoint CGPointApplyAffineTransform (
   CGPoint point,
   CGAffineTransform t
);

To get a specific point, use the view's bounds and center, and then apply the view's transform to get a new point after transform. This is better than adding code specifically for rotation transform, as it can support any transform as well as chaining.




回答4:


Try this 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);



回答5:


Why the mess and fuss? Keep it simple? Where x was before the transform, it'll be q rads/degrees further just as every other point around the anchor is.

was going to explain it all, but this chap in this post explained it in even shorter context:

Get the current angle/rotation/radian for a UIview?

CGFloat radians = atan2f(yourView.transform.b, yourView.transform.a); 
CGFloat degrees = radians * (180 / M_PI);



回答6:


I wrote this class that can help us:

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

For example, I use it to restrict the move/scale/rotation of a sticker inside a container view in the following way:

@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;
}


来源:https://stackoverflow.com/questions/19523487/find-frame-coordinates-after-uiview-transform-is-applied-cgaffinetransform

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!