[iOS]终极横竖屏切换解决方案1

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px "Helvetica Neue"; background-color: #ffffff }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px "Helvetica Neue"; color: #8c8c8c; background-color: #ffffff }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff; min-height: 18.0px }
p.p4 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: center; font: 14.0px "Helvetica Neue"; color: #b2b2b2; background-color: #ffffff }
p.p5 { margin: 0.0px 0.0px 8.0px 8.0px; text-align: center; text-indent: -8.0px; font: 16.0px "Helvetica Neue"; color: #b2b2b2; background-color: #ffffff }
p.p6 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff }
p.p7 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff }
p.p8 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: center; font: 16.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff; min-height: 18.0px }
p.p9 { margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff }
p.p10 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier; color: #3e3e3e; background-color: #f8f7f5 }
p.p11 { margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px SimSun; color: #3e3e3e; background-color: #ffffff; min-height: 18.0px }
p.p12 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 15.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff }
p.p13 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 16.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff; min-height: 18.0px }
p.p14 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 16.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff }
p.p15 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier; color: #3e3e3e; background-color: #f8f7f5; min-height: 14.0px }
p.p17 { margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px "Helvetica Neue"; background-color: #ffffff; min-height: 18.0px }
p.p18 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: center; font: 14.0px "Helvetica Neue"; color: #717375; background-color: #ffffff; min-height: 16.0px }
p.p19 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: center; font: 14.0px "Helvetica Neue"; color: #717375; background-color: #ffffff }
li.li6 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px "Helvetica Neue"; color: #3e3e3e; background-color: #ffffff }
li.li16 { margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px "Helvetica Neue"; color: #b2b2b2; background-color: #ffffff }
span.s1 { }
span.s2 { font: 12.0px "Helvetica Neue"; color: #000000 }
span.s3 { color: #607fa6 }
span.s4 { color: #007aaa }
span.s5 { font: 16.0px "Helvetica Neue" }
span.s6 { }
span.s7 { font: 16.0px "Helvetica Neue"; color: #3e3e3e }
ul.ul1 { list-style-type: disc }
ul.ul2 { list-style-type: circle }

[iOS]终极横竖屏切换解决方案

大家的项目都是只支持竖屏的吧?大多数朋友(这其中当然也包括博主),都没有做过横屏开发,这次项目刚好有这个需求,因此把横竖屏相关的心得写成一遍文章供诸位参考。

01.综述

大多数公司的项目都只支持竖屏,只有一两个界面需要同时支持横屏,就像视频 APP 一样,只有视频播放的时候需要横屏,其他时候都只允许竖屏。给出的 demo 中处理两种需要横屏的情形:

  • 第一种是录制视频时横屏
  • 第二种是播放视频时横屏

具体使用演示请前往优酷视频查看:BLLandscape Demo。

02.录制视频横屏

一般可能只需要播放视频时横屏,录制横屏一般用不到,但是如果有朋友需要做横屏视频录制,这时候就需要录制横屏处理,就像下面这样的。

这个思路是这样的:

  • 横屏的时候,首先把要横屏的 view 从原先的 superView 中移除,添加到当前的 keyWindow 上,然后做 frame 动画,将窗口的高设为 view 的宽,窗口的宽设置为 view 的高,然后将 view 的旋转 90°,执行动画,就能得到当前的效果。
  • 竖屏的时候,是一个相反的过程,先在窗口上做完动画,再将  view 插入到横屏之前的 superView 中的对应位置上。

2.1.横屏切换

我把这些实现都抽成一个 UIView 的分类,看一下实现:

// frame 转换.- (void)landscapeExecute{    self.transform

= CGAffineTransformMakeRotation(M_PI_2);    CGRect bounds

= CGRectMake(0, 0, CGRectGetHeight(self.superview.bounds),

CGRectGetWidth(self.superview.bounds));    CGPoint center

= CGPointMake(CGRectGetMidX(self.superview.bounds), CGRectGetMidY(self.

superview.bounds));    self.bounds = bounds;    self.center = center;}- (void)bl_landscapeAnimated:(BOOL)animated animations:(BLScreen

EventsAnimations)animations complete:(BLScreenEventsComplete)complete

{    if (self.viewStatus != BLLandscapeViewStatusPortrait)

{        return;    }    self.viewStatus = BLLandscapeViewStatusAnimating;

self.parentViewBeforeFullScreenSubstitute.anyObject = self.superview;

self.frame_beforeFullScreen = [NSValue valueWithCGRect:self.

frame];    NSArray *subviews = self.superview.subviews;

if (subviews.count == 1) {

self.indexBeforeAnimation = 0;    }    else{        for (int i = 0; i < subviews.count; i++)

{            id object = subviews[i];

if (object == self) {

self.indexBeforeAnimation = i;

break;            }        }    }    CGRect rectInWindow = [self.superview convertRect:self.frame

toView:nil];    [self removeFromSuperview];    self.frame = rectInWindow;    [[UIApplication sharedApplication].keyWindow addSubview:self];

if (animated) {        [UIView animateWithDuration:0.35 animations:^{            [self landscapeExecute];

if (animations) {                animations();            }        } completion:^(BOOL finished) {            [self landscpeFinishedComplete:complete];        }];    }    else{        [self landscapeExecute];        [self landscpeFinishedComplete:complete];    }    self.viewStatus = BLLandscapeViewStatusLandscape;    [self refreshStatusBarOrientation:

UIInterfaceOrientationLandscapeRight];}

2.2.竖屏切换

竖屏和横屏就是一个相反的过程,这里不贴代码也不做解释了。不懂的去看源码就知道了。

2.3.注意点

2.3.1.分类中实现 weak

源码没什么难度,但是有一个细节需要注意,我们要在分类中以 weak 的内存管理策略去引用动画之前的 superView,以便我们回来做竖屏动画完成以后将当前 view 添加到动画之前的 superView 上。但是在分类中添加属性的内存管理策略中没有 weak 属性,但是有一个 OBJC_ASSOCIATION_ASSIGN,它类似我们常用的 assign,assign 策略的特点就是在对象释放以后,不会主动将应用的对象置为 nil,这样会有访问僵尸对象导致应用奔溃的风险。

为了解决这个问题,我们可以创建一个替身对象,我们可以在分类中以 OBJC_ASSOCIATION_RETAIN_NONATOMIC 的策略来强引用替身对象,然后在替身对象中以 weak 的策略去引用我们真实需要保存的对象。这样就能解决这个可能导致奔溃的问题了。

2.3.2.布局

由于我们做的是 frame 动画,所以之后在这个 view 上再添加子控件的时候必须使用 frame 布局,Autolayout 布局在当前的 view 上将不会被更新,导致 UI 错乱。

03.播放视频横屏

大多数场景都是播放视频的时候横屏,比如下面这样的:

如果你在网上搜 iOS 横竖屏切换 能搜到的也就是播放视频的时候的横屏了,而这些文章似乎都是抄的某一篇文章,大家说的都一样。虽然大家抄来抄去,似乎他们在文章中写的都能解决问题,但实际上他们的文章是不能解决实际问题的。

3.1.播放视频横屏

我们来看一下控制屏幕旋转的两个方法:

@interface UIViewController (UIViewControllerRotation)...- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;- (UIInterfaceOrientationMask)supportedInterfaceOrientations

NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;[email protected]

可以看到可以控制屏幕方向的方法是定义在 UIViewController 里的,第一个 -shouldAutorotate 方法,系统会询问当前控制器是否支持旋转,第二个方法 -supportedInterfaceOrientations 告诉系统当前控制器支持那几个方向的旋转。

真实项目中,我们的 UI 架构可能是这样的:

我们的项目中从窗口开始,依次是一个根控制器,然后再是 UITabbarController 然后再是 UINavigationController,最后才到我们的 UIViewController,我们是某些界面需要横屏,所以必须要把系统的询问细化到每个控制

器的方法才行。

结合上图,我们看下一个横竖屏事件的传递过程:

  • 先是陀螺仪捕获到一个横屏事件
  • 接下来系统会找到当前用户操作的那个 APP
  • APP 会找到当前的窗口 window
  • 窗口 window 会找到根控制器,这个时候事件终于传到我们开发者手里了
  • 对于我们自定义的根控制器,它需要把这个事件传递到 UITabbarController
  • 对于 UITabbarController,需要把事件传递到 UINavigationController
  • 对于 UINavigationController,需要把事件传递到我们自己的控制器
  • 最后在我们自己的控制器中决定某个界面是否需要横屏

等等,你我的项目是一个已经可能有上千个控制器的大工程了,如果按照这个逻辑走下去,我们要在每个控制器写这个两个方法,不敢想象。

此时我们第一要考虑的就是借助分类来实现,既简单又优雅,而且维护起来集中干净,何乐而不为?

#import <UIKit/UIKit.h>@interface UIViewController (Landscape)/** * 是否需要横屏(默认 NO, 即当前 viewController 不支持横屏). */@property(nonatomic) BOOL bl_shouldAutoLandscape;@end#import

"UIViewController+Landscape.h"#import <objc/runtime.h>#import

<JRSwizzle.h>@implementation UIViewController (Landscape)+ (void)load{    [self jr_swizzleMethod:@selector(shouldAutorotate) withMethod:

@selector(bl_shouldAutorotate) error:nil];    [self jr_swizzleMethod:@selector(supportedInterfaceOrientations)

withMethod:@selector(bl_supportedInterfaceOrientations) error:nil];}- (BOOL)bl_shouldAutorotate{ // 是否支持旋转.    if ([self isKindOfClass:NSClassFromString(@"BLRootViewController")])

{        return self.childViewControllers.firstObject.shouldAutorotate;    }    if ([self isKindOfClass:NSClassFromString

(@"JPWarpViewController")]) {

return self.childViewControllers.firstObject.shouldAutorotate;    }    if ([self isKindOfClass:NSClassFromString(

@"UITabBarController")]) {

return ((UITabBarController *)self).selectedViewController.

shouldAutorotate;    }    if ([self isKindOfClass:NSClassFromString(

@"UINavigationController")]) {

return ((UINavigationController *)self).viewControllers.lastObject.

shouldAutorotate;    }    if ([self isKindOfClass:NSClassFromString(

@"AVFullScreenViewController")]) {

return YES;    }    if (self.bl_shouldAutoLandscape) {        return YES;    }    return NO;}- (UIInterfaceOrientationMask)bl_supportedInterfaceOrientations{

// 支持旋转的方向.    if ([self isKindOfClass:NSClassFromString(

@"BLRootViewController")]) {

return [self.childViewControllers.firstObject

supportedInterfaceOrientations];    }    if ([self isKindOfClass:NSClassFromString(

@"JPWarpViewController")]) {

return [self.childViewControllers.firstObject

supportedInterfaceOrientations];    }    if ([self isKindOfClass:NSClassFromString(

@"UITabBarController")]) {

return [((UITabBarController *)self).selectedViewController

supportedInterfaceOrientations];    }    if ([self isKindOfClass:NSClassFromString(

@"UINavigationController")]) {

return [((UINavigationController *)self).viewControllers.lastObject

supportedInterfaceOrientations];    }    if ([self isKindOfClass:NSClassFromString(

@"AVFullScreenViewController")]) {

return UIInterfaceOrientationMaskAllButUpsideDown;    }    if (self.bl_shouldAutoLandscape) {

return UIInterfaceOrientationMaskAllButUpsideDown;    }    return UIInterfaceOrientationMaskPortrait;}- (void)setBl_shouldAutoLandscape:(BOOL)bl_shouldAutoLandscape{    objc_setAssociatedObject(self, @selector(bl_shouldAutoLandscape),

@(bl_shouldAutoLandscape), OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (BOOL)bl_shouldAutoLandscape{    return [objc_getAssociatedObject

(self, _cmd) boolValue];}@end

3.2.注意点

3.2.1. JPWarpViewController

当前 demo 中使用了 JPNavigationController,因为 JPNavigationController 结构的特殊性,所以这里加了一个

if ([self isKindOfClass:NSClassFromString(@"JPWarpViewController")]) {

return [self.childViewControllers.firstObject

supportedInterfaceOrientations]; }

如果你项目中有为每个界面定制导航条的需求,你或许可以前往我的 GitHub 查看。

3.2.2.AVFullScreenViewController

当网页中有 video 标签的时候,iPhone 打开这个网页的的时候会把 video 标签替换为对应的系统的播放器,当我们点击这个视频的时候,系统会全屏进入一个视频播放界面,通过打印这个控制器我们可以看到这个控制器的类名是 AVFullScreenViewController,所以,这个界面需要横屏,就返回横屏对应的属性就可以实现这个控制器横屏。

3.2.3.实现有视频的网页需要横屏

并不是所有的网页都需要横屏,但是如果这个网页有视频,往往需要横屏,那我们怎么知道某个页面是否需要横屏,是否有视频呢?

一种方式是和 h5 约定一个事件,如果有视频就告诉原生 APP 做一个标记,将 bl_shouldAutoLandscape 置为 YES。

但是我这里提供一种更加简便优雅的方式,我们的 UIWebView 是可以通过 -stringByEvaluatingJavaScriptFromString: 方法和我们交互的,所以我们可以尝试下面的方法:

#pragma mark - UIWebViewDelegate- (void)webViewDidFinishLoad:

(UIWebView *)webView{    NSString *result = [webView

stringByEvaluatingJavaScriptFromString:@"if(document.getElementsByTagName

(‘video‘).length>0)document.getElementsByTagName(‘video‘).length;"];

if (result.length && result.integerValue != 0)

{        self.bl_shouldAutoLandscape = YES;    }}

当 WebView 加载完以后,我们去查找当前的 h5 页面中有没有 Video 标签,如果有,那我们就可以拿到结果,做对应的横屏处理。

3.2.4.大坑来了

本来我们的这个 UITableViewController 是不支持横竖屏的,就像这样,注意,这个时候那个 UISwitch 按钮是关闭的。

接下来,我们把这个开关打开,这个开关对应的代码是这样:

- (void)switchValueChanged:(UISwitch *)aswitch{

if (aswitch.isOn)

{        BLAnotherWindowViewController *vc =

[BLAnotherWindowViewController new];        self.anotherWindow.rootViewController = vc;        [self.anotherWindow insertSubview:vc.view atIndex:0];    }    else{        self.anotherWindow.rootViewController = nil;    }}

就是打开后会为另外一个窗口添加一个根控制器,而这个根控制器的代码是这样的:

#import "BLAnotherWindowViewController.h"

@interface BLAnotherWindowViewController ()@[email protected]

BLAnotherWindowViewController- (BOOL)shouldAutorotate

{    return YES;}- (UIInterfaceOrientationMask)supportedInterfaceOrientations

{    return UIInterfaceOrientationMaskAll;}@end

这样以后,我们观察一下控制器的表现:

看起来我们的主界面确实仍然不支持横竖屏,这是没有问题的,但是好像我们的状态栏被蓝色的这个窗口劫持了,它们俩双宿双飞,一起干了这么一个横竖屏的勾当。

我们想象一下,现在这个蓝色的窗口在最前面,我们能敏捷的观察到时这个蓝色的窗口劫持了状态栏。那如果这个蓝色的窗口在我们的主窗口后面呢,那我们根本就不会察觉到这个细节,我们能看到的就是下面这样:

第一次碰到这个 bug,我的内心是奔溃的。

我们一起来分析一下这个问题是怎么造成的。再来看一下这个横竖屏系统询问路径图,当我们有多个窗口之时,每个窗口都是平等的,那个蓝色的窗口也收到了系统的询问。

  • 还记得之前两个系统询问的方法是 UIViewController 的方法,此时如果窗口并没有 rootViewController 的话,那系统问也白问,所以蓝色窗口并不会劫持状态栏和横屏事件。
  • 如果此时蓝色窗口有 rootViewController 的话,那么该控制的返回值就会决定设备的方向。也就造成了这个 bug。
时间: 2024-10-02 19:54:38

[iOS]终极横竖屏切换解决方案1的相关文章

[iOS]终极横竖屏切换解决方案

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px "Helvetica Neue"; background-color: #ffffff } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px "Helvetica Neue"; color: #8c8c8c; background-color: #ffffff } p.p3 { margin: 0.0px

iOS 横竖屏切换(应对特殊需求)

iOS 中横竖屏切换的功能,在开发iOS app中总能遇到.以前看过几次,感觉简单,但是没有敲过代码实现,最近又碰到了,demo尝试了几种情况,这里就做下总结.注意 横屏两种情况是反的你知道吗? UIInterfaceOrientationLandscapeRight与UIInterfaceOrientationMaskLandscapeRight都代表横屏,Home键在右侧的情况:UIDeviceOrientationLandscapeLeft则是Home键在左侧. 一般情形 所有界面都支持横

Android横竖屏切换View设置不同尺寸或等比例缩放的XML解决方案

在一些应用中,涉及到横竖屏切换,View要切换成不同大小比例尺寸.为解决这种开发场景,有多种解决方案,比如可以重写View,实现横竖切换在onMesure或者此类View的回调方法里面重新测量重新绘制View的尺寸大小.还有可以在onConfigurationChanged里面根据当前的横竖屏切换情况重写设置View的长宽比例等等.现在给出一种比较简单且较为灵活的处理方法:通过写两套xml布局,实现在不同横竖屏切换状态下的不同大小比例尺寸.这种方案的关键做法是在res里面放置两个layout,分

【IOS界面布局】横竖屏切换和控件自适应(推荐)

[IOS界面布局]横竖屏切换和控件自适应(推荐) 分类: [MAC/IOS下开发]2013-11-06 15:14 8798人阅读 评论(0) 收藏 举报 横竖屏切换 自适应 第一种:通过人为的办法改变view.transform的属性. 具体办法: view.transform一般是View的旋转,拉伸移动等属性,类似view.layer.transform,区别在于 View.transform是二维的,也就是使用仿射的办法通常就是带有前缀CGAffineTransform的类(可以到API

iOS开发之判断横竖屏切换

/** *  当屏幕即将旋转的时候调用 * *  @param toInterfaceOrientation 旋转完毕后的最终方向 *  @param duration  旋转动画所花费的时间 */ - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { if (UIInterfaceOrientation

iOS关掉横屏后某单个页面横竖屏切换

在你想支持横竖屏的viewController里面重写两个方法: // 支持设备自动旋转 - (BOOL)shouldAutorotate {     return YES; } // 支持横竖屏显示 - (NSUInteger)supportedInterfaceOrientations {     return UIInterfaceOrientationMaskAll; } 这样在这个viewController中就可以横竖屏切换了. 但是,如果你window的rootViewContro

Android应用:横竖屏切换总结

眨眼间,已经到了2016你年春节前,离上一篇博客的时间已经有6个月多,回想起这半年的种种,不得不说,学习和工作实在是太忙了,或许这就是程序员的真实写照吧. 写博客之初,主要的目的还是为了把自己的学习痕迹记录下来,写的东西比较基础,也不多,算是一种督促,希望能坚持地学习下去,因为学识不存在暴发户,靠的是积累.如果对自己过去半年的学习给个评价,我还是比较满意的,前期定下来的目标都基本都达到了.单凭这个,我就觉得今年的新年会是个好年. 说完过去,那么接着就是将来.因为现在的工作环境上外网不大方便,而且

Android横竖屏切换重载问题与小结

(转自:http://www.cnblogs.com/franksunny/p/3714442.html) (老样子,图片啥的详细文档,可以下载后观看 http://files.cnblogs.com/franksunny/635350788930000000.pdf) Android手机或平板都会存在横竖屏切换的功能,通常是由物理重力感应触发的,但是有时候也不尽然,通常在设置里面我们可以对手机的横竖屏切换进行关闭,操作界面如下 只需要点击下“屏幕旋转”按钮就可以关闭横竖屏切换了. 一.禁止AP

android开发之activity横竖屏切换时的生命周期以及横竖屏切换时的资源适配方案

背景:之前有过两篇写activity的博客 android之activity的生命周期详解:详细介绍了activity的整个生命周期.各状态间的转换和返回桌面时保存activity的状态 android之activity中onSaveInstanceState和onRestoreInstanceState的触发时机:介绍了activity中这两个方法的触发时机和作用 本篇博客会牵扯到里面的内容,如果你都有所了解可以直接往下看,如果不了解可以进去回忆下. 问题:在做应用的退出对话框时,发现如果对话