【问题标题】:How do I calculate the affine translation parameters when rotating a view in iOS?在 iOS 中旋转视图时如何计算仿射平移参数?
【发布时间】:2013-03-25 01:10:37
【问题描述】:

我的 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];

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

【问题讨论】:

  • 感谢您的回复。我的理解是,非 Make 函数(CGAffineTransformTranslate 而不是 CGAffineTransformMakeTranslate)通过将给定参数应用于现有仿射变换来构造新的变换矩阵。
  • @user19480 你是完全正确的,我的错误,我错过了它不是 Make 类型的事实。道歉。

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


【解决方案1】:

使用 CGAffineTransform,您需要非常小心地执行操作,并且(正如 David 所说)您需要知道要旋转或调整大小的位置。人们常常对旋转的结果感到惊讶,而不管锚点(仿射旋转所围绕的点)。

如果我们有这个作为我们的起始视图:

您可以让锚点位于 (0, 0) 或图像的中心。任何一个都可以工作。两者都不可能单独给您想要的。

这里是从 0, 0:

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

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

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

【讨论】:

  • 感谢您提供的信息和非常有用的图表。是的,我需要更改视图的锚点,这使我能够计算所需的平移值。
【解决方案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];

这是更正后的代码,其中转换参数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");
    }
}

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-21
      • 1970-01-01
      • 2011-01-04
      • 2014-06-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多