禁用方向改变动画

Gre*_*reg 4 animation ios16

我需要在最近更新到 iOS 16 的设备上禁用方向更改动画。

对于较旧的 iOS 版本,有一个防止动画的解决方案。使用以下方法禁用动画后,视图层次结构立即发生变化UIView

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {

   NSLog(@"breakpoint");

   [UIView setAnimationsEnabled:NO];

   [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
                    ///
            }
                completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
                    [UIView setAnimationsEnabled:YES];
            }];
}
Run Code Online (Sandbox Code Playgroud)

更新到 iOS 16 后,动画似乎在-viewWillTransitionToSize:withTransitionCoordinator:调用之前就开始了。例如,当我在上面的代码中添加断点时,在 iOS 15(及更早版本)上,窗口在到达断点之前将保持不变。在 iOS 16 中,屏幕会在遇到断点之前旋转到新方向。

永久设置[UIView setAnimationsEnabled:NO];,在应用程序启动时会取消其他动画,但方向更改动画仍然会发生。然而,如果永久禁用动画,方向变化动画看起来会更糟,就像只有视角(旋转)有动画,但比例/方面没有动画。

Gra*_*ham 6

关键是在 iOS 16 中放弃 viewWillTransitionToSize 以获得所需的结果,而是在 PerformWithoutAnimation 块中使用 setNeedsUpdateOfSupportedInterfaceOrientations() 并覆盖supportedInterfaceOrientations。一个复杂/混乱的解决方法。

class ViewController: UIViewController {
    private var deviceOrientationObservation: NSObjectProtocol?

    //
    ... constraints
    
    var portraitConstraints: [NSLayoutConstraint] = []
    var landscapeConstraints: [NSLayoutConstraint] = []
    
    private var lastSupportedInterfaceOrientation : UIInterfaceOrientation! = .unknown
    
    
    var currentSupportedOrientation : UIInterfaceOrientation {
        let viewOrientation = self.preferredInterfaceOrientationForPresentation
        return viewOrientation
    }
    
    

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        //
        YourView.translatesAutoresizingMaskIntoConstraints = false

        self.lastSupportedInterfaceOrientation = self.currentSupportedOrientation
        
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)


        //For a solution we now completely move away from setAnimationEnabled
        //UIView.setAnimationsEnabled(false)  
        
        //Solution
        /// When the view is appearing, start listening to device orientation changes...
        deviceOrientationObservation = NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification,
                  object: UIDevice.current,
                  queue: .main, using: { [weak self] _ in

            /// When the device orientation changes to a valid interface orientation, save the current orientation as our current supported orientations.
            if let self, let mask = UIInterfaceOrientationMask(deviceOrientation: UIDevice.current.orientation), self.currentSupportedOrientations != mask {
                self.currentSupportedOrientations = mask
                
                // after the rotation
                print("mask is updated:: \(mask)")
                if (mask == .landscapeLeft || mask == .landscapeRight) {
                    //print("Landscape after")
                    self.applyLandscapeConstraints()
                } else if (mask == .portrait) {
                    //print("Portrait  after")
                    self.applyPortraitConstraints()
                } else {
                    //print("postOrientation::\(postOrientation)")
                }

                
            }
        })
        

    }
    

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        /// Stop observing when the view disappears
        deviceOrientationObservation = nil


    }

    
    private var currentSupportedOrientations: UIInterfaceOrientationMask = .portrait {
        didSet {
            /// When the current supported orientations changes, call `setNeedsUpdate...` within a `performWithoutAnimation` block.
            /// This will trigger the system to read the `supportedInterfaceOrientations` of this view controller again and apply any changes
            /// without animation, but still async.
            UIView.performWithoutAnimation {
                if #available(iOS 16.0, *) {
                    setNeedsUpdateOfSupportedInterfaceOrientations()
                } else {
                    print("get ready to fork")
                    // Fallback on earlier versions
                }
            }
        }
    }

    
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return currentSupportedOrientations
    }
    
    func applyLandscapeConstraints() {
        ...
    }
        
    func applyPortraitConstraints() {
        ...
    }
    

}



//UIDeviceOrientation.landscapeRight is assigned to UIInterfaceOrientation.landscapeLeft and UIDeviceOrientation.landscapeLeft is assigned to UIInterfaceOrientation.landscapeRight. The reason for this is that rotating the device requires rotating the content in the opposite direction.

extension UIInterfaceOrientationMask {
    init?(deviceOrientation: UIDeviceOrientation) {
        switch deviceOrientation {
        case .portrait:
            self = .portrait
        /// Landscape device orientation is the inverse of the interface orientation (see docs: https://developer.apple.com/documentation/uikit/uiinterfaceorientation)
            ///
        case .landscapeLeft:
            self = .landscapeRight
        case .landscapeRight:
            self = .landscapeLeft
        default:
            return nil
        }
    }
}
Run Code Online (Sandbox Code Playgroud)