在 7.3/9/2+ Swift 中,如何禁用设备旋转时的旋转动画?

17

本问题严格针对iOS9+。

假设您有一个普通的现代应用程序(自动布局、故事板、通用),它允许四个旋转位置。

输入图像描述

您希望它以正常方式自动旋转,因此在用户从横向旋转到纵向时,它将更改为新的基于约束的布局。

但是,您只想在用户旋转设备时不进行任何动画。您希望它只是“点击”到新的侧向或直立布局。

我唯一能够实现这一点的方法是添加:

override func viewWillTransitionToSize(size:CGSize,
       withTransitionCoordinator coordinator:UIViewControllerTransitionCoordinator)
    {
    coordinator.animateAlongsideTransition(nil, completion:
        {_ in
        UIView.setAnimationsEnabled(true)
        })
    UIView.setAnimationsEnabled(false)
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator);
    }

为了一个视图控制器,找到最高或几乎最高的VC来容纳场景中余下的容器视图或其他任何东西。

这基本上是同样古老的想法,使用willRotateToInterfaceOrientation/didRotateFromInterfaceOrientation(现在在现代iOS中都无法使用)来开启/关闭动画。

然而,存在许多问题:

  • 这不是全局的,它很混乱,是基于场景的

  • 关闭所有动画似乎非常糟糕

  • 你可以看到各种赛车赛道

此问题严格针对iOS9+

现在,有没有更好的方法来关闭支持横向/纵向的应用程序中的旋转动画?

此问题严格针对iOS9+


2
这是为哪个版本的iOS设计的? - Luke Van In
嗨@LukeVanIn,谢谢,当然是iOS9。注意第一句话! - Fattie
在模拟器上可以运行但在真实设备(iPad)上无法运行。UIView.areAnimationsEnabled() 在动画块内返回 true,这让我感到困惑(因为它确实在之前被禁用了)。这一定是旋转动画块的时间问题... (setAnimationsEnabled 文档说明:“_此方法仅影响在其调用后提交的那些动画。如果您在现有动画正在运行时调用此方法,则这些动画将继续运行,直到达到其自然结束点为止._”) - lazi74
2个回答

6
据我所知,没有更好的方法来做到这一点。
虽然你可以使用这种方法声明一个单独的类,并使你应用中的所有视图控制器成为它的子类。
class NoRotateAnimationVC: UIViewController {
    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
        UIView.setAnimationsEnabled(false)
        coordinator.notifyWhenInteractionEndsUsingBlock {_ in UIView.setAnimationsEnabled(true)}
    }
}

当您旋转设备时,所有需要更改其大小的视图控制器都会接收到“viewWillTransitionToSize”方法的调用。
您需要在应用程序中声明此新类,然后将所有视图控制器声明从类“MyViewController:UIViewController”更改为“MyViewController:NoRotateAnimationVC”。
提供的实现禁用所有动画,并在过渡后重新启用它们。因此,如果您仅在一个视图控制器中覆盖此方法,只要其视图由于旋转而改变大小,它就会在任何地方禁用旋转动画。但是,如果该视图控制器不活动,则不会禁用动画。

你需要在你的应用程序中声明这个新类,并将所有视图控制器声明从 class MyViewController: UIViewController {...} 更改为 MyViewController: NoRotateAnimationVC {...} - bzz
啊,你是说每个视图控制器都必须这样做。好的。有趣的是,我发现如果你只选择一个 - 我觉得甚至是随机的 - 它也可以工作。(虽然我不知道这是否会引入进一步的问题。) 再次感谢。 - Fattie
1
当您旋转设备时,所有需要更改其大小的视图控制器都会收到viewWillTransitionToSize方法调用。提供的实现禁用所有动画,并在过渡后重新启用它们。因此,如果您只在一个视图控制器中覆盖此方法,只要其视图由于旋转而改变大小,它就会在任何地方禁用旋转动画。但是,如果该视图控制器处于非活动状态,则不会禁用动画。 - bzz
感谢您的精彩解释,bzz。很有道理!我想如果它是“顶部”之一,并且您知道它将始终存在于应用程序中,那么可以这样做。再次感谢。 - Fattie
我可以在控制器之间过渡时禁用旋转动画吗(而不是在控制器内部)?http://stackoverflow.com/questions/42743526/can-i-transition-between-view-controllers-without-a-rotation-animation - Kartick Vaddadi

5
你可以使用方法混写
这意味着你将更改应用程序中任何视图控制器上的“viewWillTransitionToSize”调用,以调用“genericViewWillTransitionToSize”而不是原本的方法。
这样,你就不必在整个应用程序中使用子类或重复代码。
但需要注意,使用混写时应谨慎,伴随着强大的功能也有着极大的责任。将类放置在一个易于找到的位置,以便你或下一个程序员在需要返回旋转动画给视图控制器时能够找到它。
extension UIViewController {

    public override static func initialize() {
        struct Static {
            static var token: dispatch_once_t = 0
        }

        dispatch_once(&Static.token) {
            let originalSelector = #selector(UIViewController.viewWillTransitionToSize(_:withTransitionCoordinator:))
            let swizzledSelector = #selector(UIViewController.genericViewWillTransitionToSize(_:withTransitionCoordinator:))

            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
    }

    // MARK: - Method Swizzling
    func genericViewWillTransitionToSize(size:CGSize,
                                           withTransitionCoordinator coordinator:UIViewControllerTransitionCoordinator)
    {
        self.genericViewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
        coordinator.animateAlongsideTransition(nil, completion:
            {_ in
                UIView.setAnimationsEnabled(true)
        })
        UIView.setAnimationsEnabled(false)
    }
}

在 Swift 5 中,我认为这个方法已经不再适用了。 - dqualias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接