0代码解决个别界面横屏问题

2019-11-09  本文已影响0人  kagenZhao

需求

目前iOS功能类app中, 大部分都是只针对竖屏编写的UI, 不过还有少数页面需要加入横屏, 比如横屏阅读文档、视频等. 这时候就需要针对这几个页面做横屏支持.

不看文章直接用? 点我直达github

解决方案

一. 传统方案

这是目前网上比较流行的方案, 根据UI一层一层传递, 让Appdelegate知道当前页面是否需要横屏, 这里大多数人使用的是基类继承.

具体为:

  1. 创建BaseViewController, BaseTableViewController, BaseNavigationController, 并让App内部类都继承这三个基类, 并重写shouldAutorotate, supportedInterfaceOrientations, preferredInterfaceOrientationForPresentation方法并返回对应最上层Controller的相同属性
  2. AppDelegate中实现协议方法, 并返回最上层界面的supportedInterfaceOrientations
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    // 在这里返回(伪代码) 
    // tabbar.topNavigation.topController.supportedInterfaceOrientations
}
  1. 在对应的Controller中重写shouldAutorotate, supportedInterfaceOrientations, preferredInterfaceOrientationForPresentation

在这个方案中最大的问题就是对项目的侵入性较大, 使项目的耦合性增大

二. 利用runtime && category

作为iOS开发对runtime肯定都很了解, 并且应该知道category是可以覆盖原类方法的, 我正是利用这一点. 下边简述了该步骤, 代码部分不再赘述

这里要注意一点!!!, 代码要OC的, 因为Swift不支持category覆盖原有类的方法

遇到的坑

在实现方案二的同时, 也遇到了两个坑, 这里跟大家分享一下.

  1. UINavigationController进行push的时候, 默认是不会调用push出来的controller的方法的, 这里就需要用runtime重写navigation的push, 和controller的viewWillAppear来解决:

    ps: 这个项目里用到的所有runtime方法是基于RSSwizzle的, 不过因为这个库有一个小bug没有解决, 所以我把他的两个文件放到了自己的项目里, 并且解决了bug, 替换了所有方法名和类名. 不用担心会冲突.

push_bug1

解决后:


push_no_bug
  1. 在pop的时候, 如果pop了超过一个controller, 那么也会出现pop回去不会旋转屏幕的问题, 同理用runtime处理一下pop方法, 利用viewWillAppear来解决这个问题


    pop_bug1

    解决代码如下:

+ (void)rotation_hook_popToRoot {
   [KZRSSwizzle
    swizzleInstanceMethod:@selector(popToRootViewControllerAnimated:)
    inClass:UINavigationController.class
    newImpFactory:^id(KZRSSwizzleInfo *swizzleInfo) {
        NSArray<UIViewController *> *(*originalImplementation_)(__unsafe_unretained id, SEL, BOOL animated);
        SEL selector_ = @selector(popToRootViewControllerAnimated:);
        return ^NSArray<UIViewController *> * (__unsafe_unretained id self, BOOL animated) {
            if ([self viewControllers].count < 2) { return nil; }
            UIViewController *fromViewController = [self viewControllers].lastObject;
            UIViewController *toViewController = [self viewControllers].firstObject;
            if ([fromViewController rotation_fix_preferredInterfaceOrientationForPresentation] == [toViewController rotation_fix_preferredInterfaceOrientationForPresentation]) {
                return KZRSSWCallOriginal(animated);
            }
            if ([toViewController supportedInterfaceOrientations] & (1 << fromViewController.rotation_fix_preferredInterfaceOrientationForPresentation)) {
                return KZRSSWCallOriginal(animated);
            }
            __weak __typeof(toViewController) weakToViewController = toViewController;
            toViewController.rotation_viewWillAppearBlock = ^{
                __strong __typeof(weakToViewController) toViewController = weakToViewController;
                if (toViewController == nil) { return; }
                UIInterfaceOrientation ori = toViewController.rotation_fix_preferredInterfaceOrientationForPresentation;
                [toViewController rotation_forceToOrientation:ori];
                toViewController.rotation_viewWillAppearBlock = nil;
            };
            return KZRSSWCallOriginal(animated);
        };
    }
    mode:KZRSSwizzleModeAlways
    key:NULL];
}
pop_bug2
  1. 又产生了一个新的问题, 就是pop的时候动画不是那么美观, 咱们放慢来看一下:


    pop_bug2_slow

    我这里的解决方案是在pop的时候如果超过一个, 则在中间插入一个正向的临时controller
    在上一个代码的基础上修改成下面代码:

+ (void)rotation_hook_popToRoot {
    [KZRSSwizzle
     swizzleInstanceMethod:@selector(popToRootViewControllerAnimated:)
     inClass:UINavigationController.class
     newImpFactory:^id(KZRSSwizzleInfo *swizzleInfo) {
         NSArray<UIViewController *> *(*originalImplementation_)(__unsafe_unretained id, SEL, BOOL animated);
         SEL selector_ = @selector(popToRootViewControllerAnimated:);
         return ^NSArray<UIViewController *> * (__unsafe_unretained id self, BOOL animated) {
             if ([self viewControllers].count < 2) { return nil; }
             UIViewController *fromViewController = [self viewControllers].lastObject;
             UIViewController *toViewController = [self viewControllers].firstObject;
             if ([fromViewController rotation_fix_preferredInterfaceOrientationForPresentation] == [toViewController rotation_fix_preferredInterfaceOrientationForPresentation]) {
                 return KZRSSWCallOriginal(animated);
             }
             /////////////////////////// 新增代码
             if ([toViewController rotation_fix_preferredInterfaceOrientationForPresentation] == UIInterfaceOrientationPortrait) {
                 NSMutableArray<UIViewController *> * vcs = [[self viewControllers] mutableCopy];
                 InterfaceOrientationController *fixController = [[InterfaceOrientationController alloc] initWithRotation:(UIDeviceOrientation)UIInterfaceOrientationPortrait];
                 fixController.view.backgroundColor = [toViewController.view backgroundColor];
                 [vcs insertObject:fixController atIndex:vcs.count - 1];
                 [self setViewControllers:vcs];
                 return [@[[self popViewControllerAnimated:true]] arrayByAddingObjectsFromArray:KZRSSWCallOriginal(false)];
             }
             /////////////////////////// 新增代码结束
             if ([toViewController supportedInterfaceOrientations] & (1 << fromViewController.rotation_fix_preferredInterfaceOrientationForPresentation)) {
                 return KZRSSWCallOriginal(animated);
             }
             __weak __typeof(toViewController) weakToViewController = toViewController;
             toViewController.rotation_viewWillAppearBlock = ^{
                 __strong __typeof(weakToViewController) toViewController = weakToViewController;
                 if (toViewController == nil) { return; }
                 UIInterfaceOrientation ori = toViewController.rotation_fix_preferredInterfaceOrientationForPresentation;
                 [toViewController rotation_forceToOrientation:ori];
                 toViewController.rotation_viewWillAppearBlock = nil;
             };
             return KZRSSWCallOriginal(animated);
         };
     }
     mode:KZRSSwizzleModeAlways
     key:NULL];
}

结果如下:


pop_no_bug2

结尾

目前这个库中就遇到这两个问题, 解决以后比较完美

其他

目前系统的类用还有一些类有时候不能旋转, 也可以通过注册一个model来让他强制支持旋转.
比如这几个:

static inline NSArray <UIViewControllerRotationModel *> * __UIViewControllerDefaultRotationClasses() {
    NSArray <NSString *>*classNames = @[
    @"AVPlayerViewController",
    @"AVFullScreenViewController",
    @"AVFullScreenPlaybackControlsViewController",
    @"WebFullScreenVideoRootViewController",
    @"UISnapshotModalViewController",
    ];
    NSMutableArray <UIViewControllerRotationModel *> * result = [NSMutableArray arrayWithCapacity:classNames.count];
    [classNames enumerateObjectsUsingBlock:^(NSString * _Nonnull className, NSUInteger idx, BOOL * _Nonnull stop) {
        [result addObject:[[[[UIViewControllerRotationModel alloc]
                             initWithClass:className
                             containsSubClass:YES]
                            configShouldAutorotate:true]
                           configSupportedInterfaceOrientations:UIInterfaceOrientationMaskAll]];
    }];
    return result;
}

真.结尾

目前的功能就是这些, 如果有其他需求请添加Issues

最后重复一下 项目地址
文章源地址请访问我的网站

撒花~~

上一篇 下一篇

猜你喜欢

热点阅读