Найти координаты кадра после применения преобразования 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)
}
}
вы можете узнать координаты повернутого вида с помощью базовой тригонометрии. Вот как вы можете это сделать:
первый шаг-узнать ширину и высоту вашего вида. Разделите их на 2, и вы получите соседние и противоположные стороны вашего треугольника (голубой и зеленый соответственно). В примере выше width = 300 и height = 300. Таким образом, adjacentSide = 150 и oppositeSice = 150.
найти гипотенузу (красный.) Для этого вы используете формулу:
h^2 = a^2 + b^2
. После применения этой формулы находим гипотенузу = 212.13найти тета. Это угол между adjacentSide (голубой) и гипотенуза (красный). Для этого вы используете формулу
cos(theta) = (h^2 + a^2 - o^2)/2*h*o
. После применения этой формулы мы находим, что тета = 0.785 (радианы). Чтобы преобразовать это в градусы, мы применяем формулуdegrees = radians * 180 / PI
= 45 (градусов). Это начальный (смещенный) угол гипотенузы. Это очень важно осознать. ЕСЛИ ВРАЩЕНИЕ ПРЕДСТАВЛЕНИЯ РАВНО НУЛЮ, ГИПОТЕНУЗА ИМЕЕТ УГОЛ СМЕЩЕНИЯ 45 (ГРАДУСОВ). Нам скоро понадобится тета.теперь, когда мы знаем гипотенузу (красный), нам нужен rotationAngle. В этом примере я использовал
UIRotationGestureRecognizer
для поворота квадратного вида. Этот класс имеет свойство "rotation", которое говорит нам, насколько повернуто представление. Это значение находится в радианах. В приведенном выше примере вращение составляет 0,3597 Радиана. Чтобы преобразовать его в градусы формулаdegrees = radians * PI / 180
. После применения формулы мы находим, что угол поворота составляет 20,61 градуса.мы можем, наконец, найти ширину смещения (оранжевый) и высоту (фиолетовый). Для ширины используем формулу
width = cos(rotationAngle - theta) * hypotenuse
а для высоты используем формулуheight = sen(rotationAngle - theta)
. МЫ ДОЛЖНЫ ВЫЧЕСТЬ ТЕТА (В РАДИАНАХ!) ОТ УГЛА ПОВОРОТА (В РАДИАНАХ ТОЖЕ!) ПОТОМУ ЧТО ТЕТА БЫЛА НАЧАЛЬНЫМ СМЕЩЕНИЕМ. Посмотрите на это так: гипотенуза имела угол 45(градусов), когда угол поворота был равен нулю. После применения в формулах мы находим, что width = 193.20 и height = -87.60наконец, мы можем добавить эти значения (ширина и высота) в центр квадрата, чтобы найти координаты синей точки.
-пример-
// 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;
}