我需要在最近更新到 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];,在应用程序启动时会取消其他动画,但方向更改动画仍然会发生。然而,如果永久禁用动画,方向变化动画看起来会更糟,就像只有视角(旋转)有动画,但比例/方面没有动画。
关键是在 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)