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

时间:2013-03-25 01:10:38

标签: ios objective-c cgaffinetransform screen-rotation translate-animation

我的iOS应用中有UIToolbar我使用willRotateToInterfaceOrientationCGAffineTransform函数中以编程方式轮换。变换需要缩放,旋转和平移。我知道如何计算比例和旋转参数,但似乎无法弄清楚如何计算转换参数,以便视图将在正确的位置。经过大量的反复试验后,我确定了将视图移动到iPad上正确位置的确切数字,但我不想对数字进行硬编码,以免代码与设备相关。如何计算翻译参数?

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

- (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];

最后,我正在使用支持ARC的Xcode 4.5.1,并在iPad(Retina和非Retina)5.1和6.0模拟器上进行测试。

3 个答案:

答案 0 :(得分:2)

使用CGAffineTransform,你需要非常小心按照你做的顺序和(正如大卫提到的那样)你需要知道你正在旋转或调整大小的位置。人们常常对旋转的结果感到惊讶,无论anchorPoint(你用仿射旋转的点)都会感到惊讶。

如果我们在初始视图中有这个:

original view

您可以将anchorPoint设置为(0,0)或图像的中心。要么可以工作。单独的,也不会给你你想要的东西。

这是从0,0:

rotated by 90

如果您只想旋转视图以显示它,如果您有0,0锚点,则旋转并想知道“我的视图发生了什么!!!”但是你可以把它翻译成你想要的地方。

这是设置在图像中心的锚点:

rotated about center

您可以看到生成的视图,但它的垂直和水平都是关闭的,因为它是一个矩形,并且边长不同。但是,您可以通过将x移动原始高度差并将y移动原始宽度差来轻松调整。

答案 1 :(得分:2)

将工具栏的锚点设置为默认值(.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];

以下是更正后的代码,其中转换参数txty是根据工具栏和窗口大小计算的,而不是硬编码。我将逻辑包装在新的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");
    }
}

答案 2 :(得分:0)

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

有关使用边界的示例,请参阅此处:iPhone correct landscape window coordinates

相关问题