Найти координаты кадра после применения преобразования UIView (CGAffineTransform)

я поворачиваю свой вид с CGAffineTransform

[view setTransform:newTransform];

значения кадра остаются неизменными после применения преобразования, но как найти "повернутые" или преобразованные значения этого кадра?

http://www.informit.com/content/images/art_sadun_affine/elementLinks/sadun_fig02.png

мне нужны точные координаты повернутых ребер кадра, то есть A, b, c, D точек.

6 ответов


следует иметь в виду, что преобразование изменяет систему координат, поэтому вам нужно будет иметь возможность конвертировать между родительским "представлением" и дочерним (преобразованным) представлением. Кроме того, преобразования сохраняют центр преобразованного объекта, но не любые другие координаты. Поэтому вам нужно рассчитать вещи с точки зрения центра. И есть несколько помощников, которые вам понадобятся. (Я получил большую часть следующего подхода из книги Эрики Садун Core iOS Developer's Поваренная книга.)

обычно я добавляю их как категорию В UIView.

чтобы преобразовать координаты ребенка в координаты родителя, вам нужно что-то вроде:

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

вы можете узнать координаты повернутого вида с помощью базовой тригонометрии. Вот как вы можете это сделать:

enter image description here

  1. первый шаг-узнать ширину и высоту вашего вида. Разделите их на 2, и вы получите соседние и противоположные стороны вашего треугольника (голубой и зеленый соответственно). В примере выше width = 300 и height = 300. Таким образом, adjacentSide = 150 и oppositeSice = 150.

  2. найти гипотенузу (красный.) Для этого вы используете формулу:h^2 = a^2 + b^2. После применения этой формулы находим гипотенузу = 212.13

  3. найти тета. Это угол между adjacentSide (голубой) и гипотенуза (красный). Для этого вы используете формулу cos(theta) = (h^2 + a^2 - o^2)/2*h*o. После применения этой формулы мы находим, что тета = 0.785 (радианы). Чтобы преобразовать это в градусы, мы применяем формулу degrees = radians * 180 / PI = 45 (градусов). Это начальный (смещенный) угол гипотенузы. Это очень важно осознать. ЕСЛИ ВРАЩЕНИЕ ПРЕДСТАВЛЕНИЯ РАВНО НУЛЮ, ГИПОТЕНУЗА ИМЕЕТ УГОЛ СМЕЩЕНИЯ 45 (ГРАДУСОВ). Нам скоро понадобится тета.

  4. теперь, когда мы знаем гипотенузу (красный), нам нужен rotationAngle. В этом примере я использовал UIRotationGestureRecognizer для поворота квадратного вида. Этот класс имеет свойство "rotation", которое говорит нам, насколько повернуто представление. Это значение находится в радианах. В приведенном выше примере вращение составляет 0,3597 Радиана. Чтобы преобразовать его в градусы формула degrees = radians * PI / 180. После применения формулы мы находим, что угол поворота составляет 20,61 градуса.

  5. мы можем, наконец, найти ширину смещения (оранжевый) и высоту (фиолетовый). Для ширины используем формулу width = cos(rotationAngle - theta) * hypotenuse а для высоты используем формулу height = sen(rotationAngle - theta). МЫ ДОЛЖНЫ ВЫЧЕСТЬ ТЕТА (В РАДИАНАХ!) ОТ УГЛА ПОВОРОТА (В РАДИАНАХ ТОЖЕ!) ПОТОМУ ЧТО ТЕТА БЫЛА НАЧАЛЬНЫМ СМЕЩЕНИЕМ. Посмотрите на это так: гипотенуза имела угол 45(градусов), когда угол поворота был равен нулю. После применения в формулах мы находим, что width = 193.20 и height = -87.60

  6. наконец, мы можем добавить эти значения (ширина и высота) в центр квадрата, чтобы найти координаты синей точки.

-пример-

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

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

координаты голубой точки (963.20, 272.40)

чтобы лучше понять формулы, пожалуйста, смотрите следующие ссылки:

кроме того, если вы хотите поиграть с тестовым проектом, который я создал (это тот, что на изображении), пожалуйста, не стесняйтесь загружать его из следующей ссылке.

обновление

вот конденсированный метод, который вычислит смещение верхней правой точки (синий), которую вы ищете.

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

надеюсь, что это помогает!


вы должны использовать:

CGPoint CGPointApplyAffineTransform (
   CGPoint point,
   CGAffineTransform t
);

чтобы получить определенную точку, используйте границы и центр представления, а затем примените преобразование представления, чтобы получить новую точку после преобразования. Это лучше, чем добавление кода специально для преобразования вращения, поскольку он может поддерживать любое преобразование, а также цепочку.


попробуйте этот код

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

почему беспорядок и суета? Проще простого? Где x был до преобразования, это будет q рад/градусов дальше, как и любая другая точка вокруг якоря.

собирался объяснить все это, но этот парень в этом посте объяснил это в контексте еще короче:

получить текущий угол / вращение / Радиан для UIview?

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

я написал этот класс, который может помочь нам:

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.м:

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

например, я использую его для ограничения перемещения/масштабирования / вращения наклейки внутри контейнера следующим образом:

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