In 7.3/9/2+ Swift how to disable rotation animation, when device rotates?
Asked Answered
L

2

17

This question is strictly about iOS9+

Say you have an ordinary modern app (autolayout, storyboard, universal) which does allow all four rotation positions

enter image description here

you want it to autorotate in the normal way, so it will change to your new constraint-based layouts when user rotates device from landscape to portrait

But you simply want NO animation during the user rotating the device. You want it to just "click" to the new sideways or upright layout.

The only way I have been able to achieve this is by adding:

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

to one view controller, a highest or near-highest VC which holds the rest of the container views or whatever in the scene.

This is basically the same ancient idea of using willRotateToInterfaceOrientation/didRotateFromInterfaceOrientation (both now unusable in modern iOS) to turn animations on-off.

However there are many problems

  • this does not work AppWide, it is a mess, it is scene-based

  • it seems Very Bad to just turn off all animations

  • you can see all sorts of racetrack

This question is strictly about iOS9+

These days, Is there any better way to turn off the rotation animations in an app which supports landscape/portrait ???

This question is strictly about iOS9+

Lodge answered 14/7, 2016 at 18:4 Comment(3)
Which version of iOS is this for?Entoil
Hi @LukeVanIn thanks, certainly iOS9. Notice the first sentence!Lodge
Works on the simulator but not on a real device (ipad). UIView.areAnimationsEnabled() returns true inside the animation block, which puzzles me (as it is indeed disabled before). There must be a timing issue with the rotation animation block... (setAnimationsEnabled doc states:"This method affects only those animations that are submitted after it is called. If you call this method while existing animations are running, those animations continue running until they reach their natural end point.")Alumroot
C
5

You can use Method swizzling.

This means to are going to change calls to "viewWillTransitionToSize" on any view controller in your application to call "genericViewWillTransitionToSize" instead.
This way you do not have to use subclass, or repeated code over your application.

With that sad, you should be very carful with swizzling, with great power comes great responsibility. Put the class in a place that you, or the next programmer after you, will know how to find it, when he will want to return the rotation animations to view controllers.

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)
    }
}
Cabinetwork answered 4/8, 2016 at 20:26 Comment(1)
don't think this works any more in Swift 5Fortuneteller
M
6

As far as I know, there is no better way to do it.

Although you can declare a separate class with this method and make all view controllers in your app its subclasses.

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)}
    }
}

When you rotate the device, all view controllers whose views need to change their sizes receive viewWillTransitionToSize method invocation.

You need to declare this new class once in your app and then change all your view controller declarations from class MyViewController: UIViewController to MyViewController: NoRotateAnimationVC.

The provided implementation disables all animations and reenables them after the transition. So if you override this method in only one View Controller, as long as its view will change size as a result of a rotation, it will disable rotation animations everywhere. But if that view controller is not active, animations won't be disabled.

Merely answered 31/7, 2016 at 11:35 Comment(5)
You need to declare this new class once in your app and then change all your view controller declarations from class MyViewController: UIViewController {...} to MyViewController: NoRotateAnimationVC {...}Merely
Ah, you're saying EVERY view controller must do this. Fair enough. Interestingly I've found if you just pick one - i think even randomly - it does work. (I have no idea if that could introduce further problems though.) Thanks againLodge
@JoeBlow When you rotate the device, all view controllers whose views need to change their sizes receive viewWillTransitionToSize method invocation. The provided implementation disables all animations and reenables them after the transition. So if you override this method in only one View Controller, as long as its view will change size as a result of a rotation, it will disable rotation animations everywhere. But if that view controller is not active, animations won't be disabled.Merely
Thanks for that great explanation, bzz. Makes sense! I guess if it's the "top" one and you know it will always be in the app, one could do it that way. Thanks again.Lodge
Can I disable the rotation animation when transitioning between controllers (as opposed to within a controller)? #42744026Bitter
C
5

You can use Method swizzling.

This means to are going to change calls to "viewWillTransitionToSize" on any view controller in your application to call "genericViewWillTransitionToSize" instead.
This way you do not have to use subclass, or repeated code over your application.

With that sad, you should be very carful with swizzling, with great power comes great responsibility. Put the class in a place that you, or the next programmer after you, will know how to find it, when he will want to return the rotation animations to view controllers.

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)
    }
}
Cabinetwork answered 4/8, 2016 at 20:26 Comment(1)
don't think this works any more in Swift 5Fortuneteller

© 2022 - 2024 — McMap. All rights reserved.