iOS 16适配屏幕旋转强制转屏切换大总结

问题原因:

苹果又给我们挖坑了,iOS 16屏幕旋转报错:[Orientation] BUG IN CLIENT OF UIKIT: Setting UIDevice.orientation is not supported. Please use UIWindowScene.requestGeometryUpdate(_:)

解决办法:

坑1、

经过实验,以前的方法直接给UIDevice  setOrientation: 的方式还是生效的,只不过需要适配一下。

首先我们应该注意到iOS 16新增加了一个方法:setNeedsUpdateOfSupportedInterfaceOrientations

/// Notifies the view controller that a change occurred that affects supported interface orientations or the preferred interface orientation for presentation.
/// By default, this will animate any changes to orientation. To perform a non-animated update, call within `[UIView performWithoutAnimation:]`.
- (void)setNeedsUpdateOfSupportedInterfaceOrientations API_AVAILABLE(ios(16.0));

这和更新状态栏的方法有点像,简单点说就是你想转屏可以,需要通知UIViewController 你已经准备好要转屏的方向了,然后再调用转屏方法即可(转屏方法后面会讲到)。

坑2、

调用完转屏方法以后,view需要重新更新frame,这时候你会发现获取到的屏幕宽高并不是你要转屏以后的结果。难道是在iOS 16中转屏不及时更新UIScreen的size了? 可能是吧!这里我们就需要自己判断一下到底需要什么样的宽度和高度啦!

坑3、

据我实验 - (BOOL)shouldAutorotate{} 在iOS 16中不再起效果!不管返回YES还是NO都能转屏!!!反而是需要控制- (UIInterfaceOrientationMask)supportedInterfaceOrientations有效果,神奇不!!

坑4、

据我实验在iOS 16中转屏的时候,直接获取设备方向:

UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;

将返回UIDeviceOrientationUnknown,是不是神坑!!!!!

转屏方法总结:

iOS 16以前的写法:

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
    SEL selector = NSSelectorFromString(@"setOrientation:");
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
    [invocation setSelector:selector];
    [invocation setTarget:[UIDevice currentDevice]];
    int val = UIDeviceOrientationPortrait;
    [invocation setArgument:&val atIndex:2];
    [invocation invoke];
}
[UIViewController attemptRotationToDeviceOrientation];

当然,据实验所知,iOS 16以后也能用,只不过会有日志警告。

iOS 16的写法:

if (@available(iOS 16.0, *)) {
    // setNeedsUpdateOfSupportedInterfaceOrientations 方法是 UIViewController 的方法
    [self setNeedsUpdateOfSupportedInterfaceOrientations];
    NSArray *array = [[[UIApplication sharedApplication] connectedScenes] allObjects];
    UIWindowScene *scene = [array firstObject];
    // 屏幕方向
    UIInterfaceOrientationMask orientation = isLaunchScreen ? UIInterfaceOrientationMaskLandscapeRight: UIInterfaceOrientationMaskPortrait;
    UIWindowSceneGeometryPreferencesIOS *geometryPreferencesIOS = [[UIWindowSceneGeometryPreferencesIOS alloc] initWithInterfaceOrientations:orientation];
    // 开始切换
    [scene requestGeometryUpdateWithPreferences:geometryPreferencesIOS errorHandler:^(NSError * _Nonnull error) {
        NSLog(@"错误:%@", error);
    }];
}

当然如果你没有升级到Xcode 14,还可以这样写:

if (@available(iOS 16.0, *)) {
    void (^errorHandler)(NSError *error) = ^(NSError *error) {
        NSLog(@"错误:%@", error);
    };
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    SEL supportedInterfaceSelector = NSSelectorFromString(@"setNeedsUpdateOfSupportedInterfaceOrientations");
    [self performSelector:supportedInterfaceSelector];
    NSArray *array = [[UIApplication sharedApplication].connectedScenes allObjects];
    UIWindowScene *scene = (UIWindowScene *)[array firstObject];
    Class UIWindowSceneGeometryPreferencesIOS = NSClassFromString(@"UIWindowSceneGeometryPreferencesIOS");
    if (UIWindowSceneGeometryPreferencesIOS) {
        SEL initWithInterfaceOrientationsSelector = NSSelectorFromString(@"initWithInterfaceOrientations:");
        UIInterfaceOrientationMask orientation = UIInterfaceOrientationMaskPortrait;
        id geometryPreferences = [[UIWindowSceneGeometryPreferencesIOS alloc] performSelector:initWithInterfaceOrientationsSelector withObject:@(orientation)];
        if (geometryPreferences) {
            SEL requestGeometryUpdateWithPreferencesSelector = NSSelectorFromString(@"requestGeometryUpdateWithPreferences:errorHandler:");
            if ([scene respondsToSelector:requestGeometryUpdateWithPreferencesSelector]) {
                [scene performSelector:requestGeometryUpdateWithPreferencesSelector withObject:geometryPreferences withObject:errorHandler];
            }
        }
    }
#pragma clang diagnostic pop
}

注意事项:

如果只是单纯的想横屏,不想指定是向左还是向右横屏(一般是根据用户手机的方向判断)那就直接在初始化initWithInterfaceOrientations的时候传UIInterfaceOrientationMaskLandscape即可。是不是很爽~~

总结完毕!有疑问欢迎下方留言讨论!

你可能感兴趣的:(iOS,ios,xcode,objective-c)