在iOS中旋转视图时,如何计算仿射平移参数?

在iOS中旋转视图时,如何计算仿射平移参数?,ios,objective-c,cgaffinetransform,screen-rotation,translate-animation,Ios,Objective C,Cgaffinetransform,Screen Rotation,Translate Animation,我的iOS应用程序中有一个UIToolbar,我使用CGAffineTransform在willRotateToInterfaceOrientation函数中以编程方式旋转它。变换需要缩放、旋转和平移。我知道如何计算比例和旋转参数,但似乎不知道如何计算平移参数,以便视图处于正确位置。经过多次尝试和错误,我确定了将视图移动到iPad上正确位置的确切数字,但我不希望硬编码这些数字,这样代码就不会太依赖于设备。如何计算转换参数 这是我当前在包含工具栏的视图控制器中的willRotateToInterf

我的iOS应用程序中有一个UIToolbar,我使用CGAffineTransform在willRotateToInterfaceOrientation函数中以编程方式旋转它。变换需要缩放、旋转和平移。我知道如何计算比例和旋转参数,但似乎不知道如何计算平移参数,以便视图处于正确位置。经过多次尝试和错误,我确定了将视图移动到iPad上正确位置的确切数字,但我不希望硬编码这些数字,这样代码就不会太依赖于设备。如何计算转换参数

这是我当前在包含工具栏的视图控制器中的willRotateToInterfaceOrientation函数。我想知道如何计算下面的tx和ty,而不是硬编码值。我尝试过使用工具栏和窗口大小的各种功能,但是工具栏总是在一个奇怪的位置重叠窗口,或者完全在窗口之外,而不是在窗口的底部

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation))
    {
        // First, apply identify transformation to simplify later calculations and also because we need the toolbar's frame and Apple's docs say you cannot use a view's frame if the transform property is not the identity matrix.
        self.toolbar.transform = CGAffineTransformIdentity;

        // Calculate affine parameters.

        // Expand width to the window's height when in landscape mode, leaving the toolbar's height unchanged.
        UIWindow *window = [[UIApplication sharedApplication] keyWindow];
        CGFloat sx = 1.0;
        CGFloat sy = window.frame.size.height / window.frame.size.width;

        // Rotate 90 degrees in either direction depending on the orientation direction change.
        CGFloat rotate = M_PI_2;
        if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
        {
            rotate = -M_PI_2;
        }

        // Reposition the toolbar.
        // TODO: Calculate values rather than hard-coding them. Why is tx not something like ((window.frame.size.width / 2) - (self.toolbar.frame.size.height / 2))?
        // Note that these values work for both the original iPad and iPad Retina, presumably because the values represent points not pixels.
        CGFloat tx = -365.5; 
        CGFloat ty = 359.5;
        if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
        {
            tx = -tx;
        }

        // Apply affine transformation.
        CGAffineTransform transform = CGAffineTransformMakeScale(sx, sy);
        transform = CGAffineTransformRotate(transform, rotate);
        transform = CGAffineTransformTranslate(transform, tx, ty);
        [UIView animateWithDuration:duration
                         animations:^{
                                self.toolbar.transform = transform;
                            }
                         completion:NULL
         ];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
    {
        [UIView animateWithDuration:duration
                         animations:^{
                             self.toolbar.transform = CGAffineTransformIdentity;
                         }completion:NULL
         ];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
    {
        NSLog(@"Upside-down orientation not supported");
    }
}
此外,这里是我的工具栏的声明和基本初始化。请注意,我正在将工具栏作为子视图添加到主窗口并手动处理旋转,因为我的应用程序有一个UITabBarController作为根视图控制器,这是我使工具栏显示在选项卡栏顶部的唯一方法

// The toolbar is strong since this controller must maintain ownership as the toolbar is removed from the parent view when this view disappears.
@property (strong, nonatomic) IBOutlet UIToolbar *toolbar;

UIWindow *window = [[UIApplication sharedApplication] keyWindow];

CGFloat height = CGRectGetHeight(self.tabBarController.tabBar.frame); // Completely cover the tab bar.
CGFloat x = window.frame.origin.x;
CGFloat y = window.frame.size.height - height;
CGFloat width = window.frame.size.width;

self.toolbar.frame = CGRectMake(x, y, width, height);

[window addSubview:self.toolbar];

最后,我使用的Xcode 4.5.1启用了ARC,并在iPad Retina和非Retina 5.1和6.0模拟器上进行了测试。

来自UIView类参考:transform-指定应用于接收器的相对于其边界中心的变换。这意味着您的CGAffineTransform正在围绕长方体的中心旋转,而不是围绕帧原点旋转。需要相对于原点相对于旋转的位移偏移原点。这些数字是边界框尺寸的函数


有关使用边界的示例,请参见此处:

来自UIView类参考:transform-指定应用于接收器的相对于其边界中心的变换。这意味着您的CGAffineTransform正在围绕长方体的中心旋转,而不是围绕帧原点旋转。需要相对于原点相对于旋转的位移偏移原点。这些数字是边界框尺寸的函数


请参见此处使用边界的示例:

使用CGAffineTransform时,您需要非常小心操作顺序,正如David提到的,您需要知道旋转或调整大小的位置。人们经常会对旋转的结果感到惊讶,不管锚点是什么,也不管你用仿射旋转的点是什么

如果我们的出发点是:

您可以使锚点位于0、0或图像的中心。两者都可以。这两者都不可能单独给你想要的东西

这里是从0,0:

如果你只是想旋转一个视图来显示它,如果你有0,0锚定点,你旋转,想知道我的视图发生了什么!!!但是你可以把它翻译到你想要的地方

以下是将定位点设置在图像中心的示例:


您可以看到结果视图,但它在垂直和水平方向上都处于关闭状态,因为它是一个矩形,并且边的长度不同。不过,通过移动x的原始高度差和移动y的原始宽度差,您可以轻松地进行调整。

使用CGAffineTransform,您需要非常小心操作顺序,正如David提到的,您需要知道旋转或调整大小的位置。人们经常会对旋转的结果感到惊讶,不管锚点是什么,也不管你用仿射旋转的点是什么

如果我们的出发点是:

您可以使锚点位于0、0或图像的中心。两者都可以。这两者都不可能单独给你想要的东西

这里是从0,0:

如果你只是想旋转一个视图来显示它,如果你有0,0锚定点,你旋转,想知道我的视图发生了什么!!!但是你可以把它翻译到你想要的地方

以下是将定位点设置在图像中心的示例:


您可以看到结果视图,但它在垂直和水平方向上都处于关闭状态,因为它是一个矩形,并且边的长度不同。不过,您可以通过按原始高差移动x和按原始宽度差移动y来轻松地进行调整。

在将工具栏的定位点从默认值.5、.5设置为0、0后,我能够解决此问题

// The toolbar is strong since this controller must maintain ownership as the toolbar is removed from the parent view when this view disappears.
@property (strong, nonatomic) IBOutlet UIToolbar *toolbar;

UIWindow *window = [[UIApplication sharedApplication] keyWindow];

// Set anchor point to the top-left corner instead of to the center (.5, .5) so that affine transformations during orientation changes are easier to calculate.
self.toolbar.layer.anchorPoint = CGPointMake(0, 0);

CGFloat height = CGRectGetHeight(self.tabBarController.tabBar.frame); // Completely cover the tab bar.
CGFloat x = window.frame.origin.x;
CGFloat y = window.frame.size.height - height;
CGFloat width = window.frame.size.width;

self.toolbar.frame = CGRectMake(x, y, width, height);

[window addSubview:self.toolbar];
下面是更正的代码,其中转换参数tx和ty是根据工具栏和窗口大小计算的,而不是硬编码。我把逻辑包装在新的willRotateView:t中 oInterfaceOrientation:duration:函数使重用和旋转任意视图更容易。 willRotateToInterfaceOrientation:duration:将“我的工具栏”作为视图参数,将旋转请求转发到此函数

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self willRotateView:self.toolbar toInterfaceOrientation:toInterfaceOrientation duration:duration];
}

- (void)willRotateView:(UIView *)view toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation))
    {
        // First, apply identify transformation to simplify later calculations and also because we need the view's frame and Apple's docs say you cannot use a view's frame if the transform property is not the identity matrix.
        view.transform = CGAffineTransformIdentity;

        // Calculate affine parameters.

        // Get the window's post-orientation change dimensions.
        UIWindow *window = [[UIApplication sharedApplication] keyWindow];
        CGFloat rotatedWinWidth = window.frame.size.height;
        CGFloat rotatedWinHeight = window.frame.size.width;

        // Expand width to the window's height when in landscape mode, leaving the view's height unchanged. The scaling is done along the Y axis since the window's origin will change such that the Y axis will still run along the longer direction of the device.
        CGFloat sx = 1.0;
        CGFloat sy = rotatedWinWidth / rotatedWinHeight;

        // Rotate 90 degrees in either direction depending on the orientation direction change.
        CGFloat angle = M_PI_2;
        if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
        {
            angle = -M_PI_2;
        }

        // Reposition the view, assuming that view.layer.anchorPoint is (0, 0).
        // Note that the height of the view is used as the X offset as this corresponds to the X direction since the rotated window's origin will also rotate. Also, the position has to take into account the width scale factor.
        CGFloat xOffset = view.frame.size.height;
        CGFloat tx = -(rotatedWinWidth - xOffset) / sy;
        CGFloat ty = -view.frame.size.height;
        if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
        {
            tx = -xOffset / sy;
            ty = rotatedWinHeight - view.frame.size.height;
        }

        // Apply affine transformation.
        CGAffineTransform transform = CGAffineTransformMakeScale(sx, sy);
        transform = CGAffineTransformRotate(transform, angle);
        transform = CGAffineTransformTranslate(transform, tx, ty);
        [UIView animateWithDuration:duration
                         animations:^{
                             view.transform = transform;
                         }
                         completion:NULL
         ];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
    {
        [UIView animateWithDuration:duration
                         animations:^{
                             view.transform = CGAffineTransformIdentity;
                         }completion:NULL
         ];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
    {
        DLog(@"Upside-down orientation not supported");
    }
}

在将工具栏的锚点从默认值.5、.5设置为0,0后,我能够解决这个问题

// The toolbar is strong since this controller must maintain ownership as the toolbar is removed from the parent view when this view disappears.
@property (strong, nonatomic) IBOutlet UIToolbar *toolbar;

UIWindow *window = [[UIApplication sharedApplication] keyWindow];

// Set anchor point to the top-left corner instead of to the center (.5, .5) so that affine transformations during orientation changes are easier to calculate.
self.toolbar.layer.anchorPoint = CGPointMake(0, 0);

CGFloat height = CGRectGetHeight(self.tabBarController.tabBar.frame); // Completely cover the tab bar.
CGFloat x = window.frame.origin.x;
CGFloat y = window.frame.size.height - height;
CGFloat width = window.frame.size.width;

self.toolbar.frame = CGRectMake(x, y, width, height);

[window addSubview:self.toolbar];
下面是更正的代码,其中转换参数tx和ty是根据工具栏和窗口大小计算的,而不是硬编码。我在新的willRotateView:toInterfaceOrientation:duration:函数中封装了该逻辑,以便更容易重用和旋转任意视图。 willRotateToInterfaceOrientation:duration:将“我的工具栏”作为视图参数,将旋转请求转发到此函数

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self willRotateView:self.toolbar toInterfaceOrientation:toInterfaceOrientation duration:duration];
}

- (void)willRotateView:(UIView *)view toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation))
    {
        // First, apply identify transformation to simplify later calculations and also because we need the view's frame and Apple's docs say you cannot use a view's frame if the transform property is not the identity matrix.
        view.transform = CGAffineTransformIdentity;

        // Calculate affine parameters.

        // Get the window's post-orientation change dimensions.
        UIWindow *window = [[UIApplication sharedApplication] keyWindow];
        CGFloat rotatedWinWidth = window.frame.size.height;
        CGFloat rotatedWinHeight = window.frame.size.width;

        // Expand width to the window's height when in landscape mode, leaving the view's height unchanged. The scaling is done along the Y axis since the window's origin will change such that the Y axis will still run along the longer direction of the device.
        CGFloat sx = 1.0;
        CGFloat sy = rotatedWinWidth / rotatedWinHeight;

        // Rotate 90 degrees in either direction depending on the orientation direction change.
        CGFloat angle = M_PI_2;
        if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
        {
            angle = -M_PI_2;
        }

        // Reposition the view, assuming that view.layer.anchorPoint is (0, 0).
        // Note that the height of the view is used as the X offset as this corresponds to the X direction since the rotated window's origin will also rotate. Also, the position has to take into account the width scale factor.
        CGFloat xOffset = view.frame.size.height;
        CGFloat tx = -(rotatedWinWidth - xOffset) / sy;
        CGFloat ty = -view.frame.size.height;
        if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
        {
            tx = -xOffset / sy;
            ty = rotatedWinHeight - view.frame.size.height;
        }

        // Apply affine transformation.
        CGAffineTransform transform = CGAffineTransformMakeScale(sx, sy);
        transform = CGAffineTransformRotate(transform, angle);
        transform = CGAffineTransformTranslate(transform, tx, ty);
        [UIView animateWithDuration:duration
                         animations:^{
                             view.transform = transform;
                         }
                         completion:NULL
         ];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
    {
        [UIView animateWithDuration:duration
                         animations:^{
                             view.transform = CGAffineTransformIdentity;
                         }completion:NULL
         ];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
    {
        DLog(@"Upside-down orientation not supported");
    }
}

谢谢你的回复。我的理解是,非Make函数CgaffinetTransformTranslate而不是CgaffinetTransformMakeTranlate通过将给定参数应用于现有仿射变换来构造新的变换矩阵。@user19480您完全正确,我的错误,我忽略了它不是Make类型的事实。抱歉,谢谢你的回复。我的理解是,非Make函数CgaffinetTransformTranslate而不是CgaffinetTransformMakeTranlate通过将给定参数应用于现有仿射变换来构造新的变换矩阵。@user19480您完全正确,我的错误,我忽略了它不是Make类型的事实。抱歉。谢谢你提供的信息和非常有用的图表。是的,我需要更改视图的定位点,这使我能够计算所需的转换值。感谢您提供的信息和非常有用的图表。是的,我需要更改视图的定位点,这使我能够计算所需的平移值。