iOS 16 屏幕旋转适配

本文主要解决这个问题:Error Domain=UISceneErrorDomain Code=101 "None of the requested orientations are supported by the view controller. Requested: landscapeRight; Supported: portrait"

关于报错

Error Domain=UISceneErrorDomain Code=101 "None of the requested orientations are supported by the view controller. Requested: landscapeRight; Supported: portrait"

这个报错卡了很久,网上也没搜到相应的解决方案。plist 文件也检查过了,设置了多个方向,还是报错。

核心解决点有两点:

  1. 在iOS 16上 需要完成 AppDelegate 内的回调 - (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window众所周知,这个函数的优先级是高于plist的。
  2. 在iOS 16屏幕旋转前,调用UIViewController.attemptRotationToDeviceOrientation(), 这点很重要,报错不支持旋转的方向,就是因为没有调用这个函数引起的。

iOS 16 屏幕旋转通知

看到网上有人说屏幕旋转通知收不到了,其实是可以的。只是需要开启和关闭监听。

UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(screenChangedOrientation(_:)), name: UIDevice.orientationDidChangeNotification, object: nil)
UIDevice.current.endGeneratingDeviceOrientationNotifications()

核心代码

ViewController 中

/// 强制转横坚屏操作
    /// - Parameter landscape: 是否转横屏
    private func forceDeviceRotaiton(landscape: Bool) {
        ...
        ...
        guard #available(iOS 16.0, *) else {
            if landscape {
                UIDevice.current.setValue(NSNumber.init(value: UIInterfaceOrientation.unknown.rawValue), forKey: "orientation")
                UIDevice.current.setValue(NSNumber.init(value: UIInterfaceOrientation.landscapeRight.rawValue), forKey: "orientation")
            } else {
                UIDevice.current.setValue(NSNumber.init(value: UIInterfaceOrientation.unknown.rawValue), forKey: "orientation")
                UIDevice.current.setValue(NSNumber.init(value: UIInterfaceOrientation.portrait.rawValue), forKey: "orientation")
            }
            return
        }
        switchMode(full: landscape)
    }
    
    @available(iOS 16.0, *)
    private func switchMode(full: Bool) {
        DispatchQueue.main.async {
            guard
                let scence = UIApplication.shared.connectedScenes.first as? UIWindowScene
            else {
                return
            }
            
            NotificationCenter.default.post(name: NSNotification.Name("Notification_isLandscape"), object: NSNumber.init(booleanLiteral: full))
            
            UIViewController.attemptRotationToDeviceOrientation()
            
            let orientation: UIInterfaceOrientationMask = full ? .landscapeRight : .portrait
            let geometryPreferencesIOS = UIWindowScene.GeometryPreferences.iOS(interfaceOrientations: orientation)

            scence.requestGeometryUpdate(geometryPreferencesIOS) { error in
                print("debug \(error)")
            }
            
            self.navigationController?.topViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
        }
    }

AppDelegate 中

#import <ReactiveObjC/ReactiveObjC.h>

@property (nonatomic, assign) BOOL isLandscape;

- (void)applicationForSafeMode:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...
     @weakify(self)
    [[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"Notification_isLandscape" object:nil] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(NSNotification * _Nullable sender) {
        @strongify(self)
        NSNumber *notification_isLandscape = sender.object;
        self.isLandscape = notification_isLandscape.boolValue;
        
    }];
    self.isLandscape = NO;
}

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    if (@available(iOS 16.0, *)) {
        if(self.isLandscape == YES){
            return UIInterfaceOrientationMaskLandscape;
        }
        return UIInterfaceOrientationMaskPortrait;
    }
    
    return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscape;
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    
    if(self.isLandscape){
        if (@available(iOS 16.0, *)) {
            [UIViewController attemptRotationToDeviceOrientation];
            [[NSNotificationCenter defaultCenter] postNotificationName:@"Notification_isLandscape" object:@(YES)];
            NSArray *array = [[UIApplication sharedApplication].connectedScenes allObjects];
            UIWindowScene *scene = (UIWindowScene *)[array firstObject];
            UIWindowSceneGeometryPreferencesIOS *geometryPreferences = [[UIWindowSceneGeometryPreferencesIOS alloc] initWithInterfaceOrientations:UIInterfaceOrientationMaskLandscape];
            [scene requestGeometryUpdateWithPreferences:geometryPreferences errorHandler:^(NSError * _Nonnull error) {}];
        }
    }
    ...
}