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

Я поворачиваю взгляд с помощью CGAffineTransform

[view setTransform:newTransform];

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


(источник: informit.com)

Мне нужны точные координаты повернутых краев кадра, то есть точки a, b, c, d.


person Vad    schedule 22.10.2013    source источник
comment
Вам не нужно делать никаких триггеров. Вы должны использовать CGPointApplyAffineTransform() или CGRectApplyAffineTransform(), как предлагает @LeoNatan @Bladebunny et al.   -  person nielsbot    schedule 01.11.2013
comment
Все эти ответы запутаны и длинны без всякой причины, Apple предоставляет оптимизированный способ сделать это в одной строке кода (начиная с iOS2), я рекомендую вам обновить выбранный ответ: stackoverflow.com/a/66096889/2057171   -  person Albert Renshaw    schedule 08.02.2021


Ответы (7)


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

Я обычно добавляю их как категорию в 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)
    }
}
person Bladebunny    schedule 30.10.2013
comment
Не могли бы вы дать этот ответ stackoverflow.com/questions/34763819/ - person Hardik Vyas; 13.01.2016
comment
Означает ли это, что (например) CGAffineTransformTranslate переводит систему координат объекта, а не объект в своей системе координат? - person user5539357; 11.12.2016
comment
@Bladebunny, можете ли вы сказать коду, чтобы получить центральную точку каждой стороны TOP, RIGHT, BOTTOM, LEFT? - person john afordis; 14.12.2016

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

введите описание изображения здесь

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

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

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

  4. Теперь, когда мы знаем гипотенузу (красная), нам нужен угол поворота. В этом примере я использовал UIRotationGestureRecognizer для поворота квадратного вида. У этого класса есть свойство «поворота», которое сообщает нам, насколько повернуто представление. Это значение в РАДИАНАХ. В приведенном выше примере вращение составляет 0,3597 РАДИАНОВ. Чтобы преобразовать его в градусы, мы используем формулу degrees = radians * PI / 180. После применения формулы угол поворота составляет 20,61 градуса.

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

Надеюсь это поможет!

person LuisCien    schedule 27.10.2013
comment
Я резюмировал, но это не совсем работает: CGFloat смежный = rte.frame.size.width / 2.0; CGFloat Against = rte.frame.size.height / 2.0; CGFloat hipotenuse = sqrtf (powf (смежный, 2.0) + pow (напротив , 2.0)); CGFloat theta = acosf ((powf (hipotenuse, 2.0) + powf (смежный, 2.0) -pow (напротив, 2.0)) / (2 * hipotenuse * смежный)); theta = theta * 180 / M_PI; CGFloat радианы = atan2f (rte.transform.b, rte.transform.a); CGFloat градусов = радианы * (180 / M_PI); CGFloat width = cosf (градусы - тета) * hipotenuse; CGFloat height = sinf (градусы - тета) * hipotenuse; rightTopNode.center = CGPointMake (rte.center.x + ширина, rte.center.y + высота); - person Vad; 28.10.2013
comment
Привет @Vad. Вы правильно поняли мое объяснение. Ошибка в вашем коде (и это потому, что я забыл объяснить эту часть), потому что параметры для cosf () и sinf () для вычисления ширины и высоты должны быть в радианах. Также см. Мой обновленный ответ. Я добавил сжатый метод, который будет вычислять координаты точки за вас. Наконец, я не уверен, почему вы используете atan2f. Вы упомянули, что используете преобразование вращения для своего представления. Функция преобразования поворота принимает угол в радианах для поворота. Вы должны использовать тот же угол для выполнения своих расчетов. Надеюсь это поможет - person LuisCien; 29.10.2013
comment
В некоторые моменты я не могу использовать преобразование вращения и хочу вычислить радианы, используя свойства преобразованного объекта - поэтому у меня был atan2f (rte.transform.b, rte.transform.a); Разве это не одно и то же? Спасибо. - person Vad; 29.10.2013
comment
Привет @Vad, извиняюсь за поздний ответ. Я только что протестировал подход, используя atan2f, и он работает. Просто не забудьте использовать радианы при вычислении width и height. Не конвертируйте theta в радианы и используйте результат atan2f напрямую, так как эта функция возвращает радианы. - person LuisCien; 30.10.2013
comment
не используйте это - используйте CGPointApplyAffineTransform() или CGRectApplyAffineTransform(), как предлагает @Bladebunny @ LeoNatan и т. д. - person nielsbot; 01.11.2013
comment
Почему-то у меня это никогда не срабатывало, но я должен отдать должное этому решению, так как оно мне очень помогло. Спасибо @LuisCien. Я выберу полное решение ниже, которое безупречно сработало для меня и является полным (для каждой граничной точки). Я очень ценю ваш вклад. - person Vad; 02.11.2013

Вам следует использовать:

CGPoint CGPointApplyAffineTransform (
   CGPoint point,
   CGAffineTransform t
);

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

person Leo Natan    schedule 27.10.2013

Все эти ответы чокнутые, это так просто ...

CGPoint topLeft = [rotatedView convertPoint:CGPointMake(0, 0) toView:rotatedView.superview];

CGPoint topRight = [rotatedView convertPoint:CGPointMake(rotatedView.bounds.size.width, 0) toView:rotatedView.superview];

CGPoint bottomLeft = [rotatedView convertPoint:CGPointMake(0, rotatedView.bounds.size.height) toView:rotatedView.superview];

CGPoint bottomRight = [rotatedView convertPoint:CGPointMake(rotatedView.bounds.size.width, rotatedView.bounds.size.height) toView:rotatedView.superview];
person Albert Renshaw    schedule 08.02.2021

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

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);
person MarekR    schedule 31.10.2013

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

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

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

CGFloat radians = atan2f(yourView.transform.b, yourView.transform.a); 
CGFloat degrees = radians * (180 / M_PI);
person Adrian Sluyters    schedule 13.01.2016

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

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

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

@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;
}
person kanobius    schedule 15.07.2016