www.2527.com_澳门新葡8455手机版_新京葡娱乐场网址_
做最好的网站

计算机编程横屏竖屏自适应计算,从ViewControlle

2019-09-17 02:13 来源:未知

了解更多,请关注我的微信公众号:mellong

      在 iPhone 应用里,有时我们想强行把显示模式从纵屏改为横屏(反之亦然),特别是需要展示音频,视频播放器的时候需要进行横竖屏切换,下面做下横竖屏总结,如有不全面之处欢迎留言补充。

转自:

所有frame的高度和宽度应该通过superview的bounds计算。xib中的view无法设置auto mask的必须通过代码设,不设定的话有时可以自动适应,但是有时会出现有部分黑屏的情况。两边都不设置mask则为居中显示。

    目前横竖屏旋转方式分为手动和自动

标签: 转屏 shouldautorotate frame viewcontroller 分类: iOS开发笔记

以下两方法为rotate是自动调用,如果该viewController没有navigationController时,以下两方法可能不被调用,需要自己加入通知中心。

手动旋转
手动旋转也有2种方式,一种是直接设置 UIDevice 的 orientation,但是这种方式不推荐,上传appStore有被拒的风险:

文字罗嗦,篇幅较长,只需营养可直接看红字部分。

- didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

复制代码 代码如下:

一个viewController的初始化大概涉及到如下几个方法的调用:

调用此方法时superview.bounds已经改变。

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {  
    [[UIDevice currentDevice] performSelector:@selector(setOrientation:) withObject:(id)UIInterfaceOrientationPortrait];  
}

initWithNibName:bundle:

- willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

  
第二种是假旋转,并没有改变 UIDevice 的 orientation,而是改变某个view的 transform,利用 CGAffineTransformMakeRotation 来达到目的,比如:

viewDidLoad

调用此方法时superview.bounds未改变

复制代码 代码如下:

viewWillAppear:animated:

获取当前屏幕方向

self.view.transform = CGAffineTransformMakeRotation(M_PI/2)  

viewDidAppear:animated:

UIInterfaceOrientation currentOrient = [UIApplication sharedApplication].statusBarOrientation;

下面讲解采用第二种方式的各版本手动旋转:
思想是首先设置 statusBarOrientation,然后再改变某个view的方向跟 statusBarOrientation 一致!

viewWillLayoutSubviews

判断当前设备是否为4寸屏

那既然是旋转,最少也得有2个方向,那么还是少不了上面说的那个硬性条件,先在plist里面设置好所有可能需要旋转的方向。既然是手动旋转,那么就要关闭自动旋转:

viewDidLayoutSubviews

#define isIPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)

view.transform一般是View的旋转,拉伸移动等属性,类似view.layer.transform,区别在于View.transform是二维的,也就是使用仿射的办法通常就是带有前缀CGAffineTransform的类(可以到API文档里面搜索这个前缀的所有类),而view.layer.transform可以在3D模式下面的变化,通常使用的都是前缀为CATransform3D的类。

通常情况这几个方法是依次被调用的,我们会在init方法中初始化一些成员变量,做一些与view无关的事情。而后在viewDidLoad中进行view布局相关的属性调整,比如改变一下背景颜色,增加一些subview之类的。不知道大家有没有想过,这样不在init中写view相关代码是为了什么?难道仅仅是为了代码结构清晰?如果我非要在init做一些与view相关的初始化工作,能不能实现?有什么问题?

3.5与4寸屏高度相差88.f,宽度一样为320.f自适应横屏一般修改automask的autowidth,导航栏和一般控件主要变化的是宽度,高度也变化的一般是可以tableView和scrollView等。

 

@implementation testViewController

有时候横屏没有正确自适应一般是superview.bounds未改变,设置subview frame的时机不对。

这里要记住一点,当你改变过一个view.transform属性或者view.layer.transform的时候需要恢复默认状态的话,记得先把他们重置可以使用view.transform = CGAffineTransformIdentity,或者view.layer.transform = CATransform3DIdentity,假设你一直不断的改变一个view.transform的属性,而每次改变之前没有重置的话,你会发现后来的改变和你想要的发生变化了,不是你真正想要的结果。

- (void)printFrame:(CGRect)frame name:(NSString *)name

  • 如果想让某一个ViewController固定某个方向不旋转,方法如下:

 

{

  1. 修改AppDelegate.m,加入下列代码,其中_enablePortrait为新增的变量,用于判断是否要进行旋转。

好了,上面介绍了旋转的属性,接下来就是关键了。官方提供了一个办法就是查看当前电池条的状态UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;通过这个办法,你可以知道当前屏幕的电池条的显示方向,而且你还可以强制设置他的显示方向,通过设置这个属性就OK了,可以选择是否动画改变电池条方向。有了这两个那我们就可以任意的改变我们想要的显示方式了。

NSLog(@"%@ :(%f, %f, %f, %f)", name, frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);

 

}

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{ if(_enablePortrait) { return UIInterfaceOrientationMaskPortrait; } return UIInterfaceOrientationMaskLandscape | UIInterfaceOrientationMaskPortrait;}

1.获取当前电池条的方向UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

  1. 在不需要旋转的viewController中的下列方法中加入以下代码即可。

 

{

2.获取当前屏幕的大小CGRect frame = [UIScreen mainScreen].applicationFrame;

self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

-viewWillAppear:animated{ AppDelegate *delegate = (AppDelegate *)[UIApplicationsharedApplication].delegate; delegate.enablePortrait = YES;}- viewWillDisappear:animated{ [super viewWillDisappear:animated]; AppDelegate *delegate = (AppDelegate *)[UIApplicationsharedApplication].delegate; delegate.enablePortrait = NO;}

 

if (self) {

由于使用pushViewController会导致所进入的视图会根据前一视图的方向显示,所以需要用以下方法hack一下,才能使其自动根据设定的方向旋转。

3.设置我们的View的中心点
CGPoint center = CGPointMake(frame.origin.x ceil(frame.size.width/2), frame.origin.y ceil(frame.size.height/2));

// Custom initialization

- updateOrientation{ [[UIApplicationsharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortraitanimated:NO]; UIViewController *viewController = [[UIViewControlleralloc] init]; [self presentModalViewController:viewController animated:NO]; [self dismissModalViewControllerAnimated:NO]; [viewController release];}

 

self.view.backgroundColor = [UIColor yellowColor];

  • iOS6旋转发生当时屏幕不旋转的原因可能是:

4.根据当前电池条的方向,获取需要旋转的角度的大小。通常

[self printFrame:self.view.frame name:@"initFrame"];

 

}

 if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO { self.window.rootViewController = gameNavController; }else { [self.window addSubview:gameNavController.view]; }

if (orientation == UIInterfaceOrientationLandscapeLeft) {
return CGAffineTransformMakeRotation(M_PI*1.5);
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
return CGAffineTransformMakeRotation(M_PI/2);
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
return CGAffineTransformMakeRotation(-M_PI);
} else {
return CGAffineTransformIdentity;
}

return self;

在应用中有时需要制定某些页面是Portrait或者landscape,这时需要在info.plist文件加入对这些方向的支持。如果window的rootViewController是NavigationController则需继承该类写入:

 

}

//iOS6以下版本- shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{ return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);}//iOS6及以上版本- shouldAutorotate{ return YES;}- (NSUInteger)supportedInterfaceOrientations{ return UIInterfaceOrientationMaskLandscape;}

5.可以动画的改变我们view的显示方式了
[[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationLandscapeRight animated:YES];

- (void)viewDidLoad

由此则全局默认情况下只支持landscape。注意:navigationController在其子类中指定,在push进去的viewController指定则是无效。

CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;(获取当前电池条动画改变的时间)
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
//在这里设置view.transform需要匹配的旋转角度的大小就可以了。
[UIView commitAnimations];

{

有效的情况为使用presentModalViewController或者其他形式的present,在present的viewController中重写这三个方法,可以限制其当前的方向只为portrait.

复制代码 代码如下:

[super viewDidLoad];

- shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{ return UIInterfaceOrientationPortrait == toInterfaceOrientation;}- shouldAutorotate{ return YES;}- (NSUInteger)supportedInterfaceOrientations{ return UIInterfaceOrientationMaskPortrait;}
  • (BOOL)shouldAutorotate{  
            return NO;  
    }

// Do any additional setup after loading the view.

  
手动触发某个按钮,调用方法,这个方法的实现如下:

[self printFrame:self.view.frame name:@"didloadFrame"];

复制代码 代码如下:

}

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];  
self.view.transform = CGAffineTransformMakeRotation(M_PI/2);  
self.view.bounds = CGRectMake(0, 0, kScreenHeight, 320);   

- (void)viewWillLayoutSubviews

注意:
1. 只需要改变self.view.transform,那么self.view的所有subview都会跟着自动变;其次因为方向变了,所以self.view的大小需要重新设置,不要使用self.view.frame,而是用bounds。
2. 如果shouldAutorotate 返回YES的话,下面设置setStatusBarOrientation 是不管用的!setStatusBarOrientation只有在shouldAutorotate 返回NO的情况下才管用!

{

自动旋转设置:
控制某个viewController旋转并不是像IOS5或者IOS4一样在这个viewController里面重写上面那2个方法,而是需要在这个viewController的rootViewController(根视图控制器)里面重写,怎么解释呢?就是最前面的那个viewController,直接跟self.window接触的那个controller,比如以下代码:

[super viewWillLayoutSubviews];

复制代码 代码如下:

[self printFrame:self.view.frame name:@"willLayoutFrame"];

UIViewController *viewCtrl = [[UIViewController alloc] init];  
UINavigationController *navCtrl = [[UINavigationController alloc] initWithRootViewController:viewCtrl];  
if ([window respondsToSelector:@selector(setRootViewController:)]) {  
    self.window.rootViewController = navCtrl;  
} else {  
    [self.window addSubview:navCtrl.view];  
}  

}

如果需要设置viewCtrl的旋转,那么不能在UIViewController里面重写shouldAutorotate和supportedInterfaceOrientations方法,而是需要在navCtrl里面设置,又因为UINavigationController是系统控件,所以这里需要新建一个UINavigationController的子navigationController的子类,然后在里面实现shouldAutorotate和supportedInterfaceOrientations方法,比如:

-(void)viewDidLayoutSubviews

复制代码 代码如下:

{

-(NSUInteger)supportedInterfaceOrientations{  
    return UIInterfaceOrientationMaskAllButUpsideDown;  
}     

[super viewDidLayoutSubviews];

  • (BOOL)shouldAutorotate{  
        return YES;  

[self printFrame:self.view.frame name:@"didLayoutFrame"];

eg1:如果上面的例子是self.window.rootViewController

viewCtrl,而不是navCtrl,那么上面的那2个控制旋转的方法就应该写在UIViewController里面!

eg2:如果viewCtrl又pushViewController到viewCtrl2,需要设置viewCtrl2的旋转,怎么办呢? 还是在navCtrl里面控制,因为viewCtrl和viewCtrl2的rootViewController都是navCtrl,一般的写法都是

复制代码 代码如下:

UIViewController *viewCtrl2 = [[UIVewController alloc] init];  
[self.navigationController.navigationController pushViewController:viewCtrl2 animated:YES]; 

所以要控制一个UINavigationController push到的所有viewController的旋转,那么就得在navCtrl里面区分是哪个viewController,以便对他们一一控制!同样如果rootViewController是UITabbarController,那么需要在子类化的UITabbarController里面重写那2个方法,然后分别控制!

但是有时候我初始化UINavigationController的时候,并不知道所有我所有需要push到的viewController,那么这里有一个通用的方法,就是让viewController自己来控制自己,首先在navCtrl里面的实现方法改为以下方式:

复制代码 代码如下:

  • (BOOL)shouldAutorotate    
    {    
        return self.topViewController.shouldAutorotate;    
    }         
  • (NSUInteger)supportedInterfaceOrientations    
    {    
        return self.topViewController.supportedInterfaceOrientations;    
    }  

全部调用self.topViewController,就是返回当前呈现出来的viewController里面的设置,然后在viewCtrl、viewCtrl2等等这些viewController里面重写shouldAutorotate和supportedInterfaceOrientations,以方便设置每个viewController的旋转

eg3:如果viewCtrl 是 presentModalViewController 到 viewCtrl3,那么viewCtrl3的旋转设置就不在navCtrl里面了!如果presentModalViewController的viewController是navController、tabbarController包装过的viewCtrl3,那么就应在新包装的navController、tabbarController里面设置,如果是直接presentModalViewController到viewCtrl3,那么就在viewCtrl3里面设置

通过setOrientation:的办法强制性的旋转到一个特定的方向。

 

注意:Apple在3.0以后都不支持这个办法了,这个办法已经成为了私有的了,但是要跳过App Stroe的审核,需要一点巧妙的办法。

 

不要直接调用[[UIDevice currentDevice] setOrientation: UIInterfaceOrientationLandscapeRight]这样的办法来强制性的横屏,这样导致你的程序是很难通过App Store审核的。但是你可以选择使用performSelector的办法来调用它。具体就几行代码如下:

 

//强制横屏
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
[[UIDevice currentDevice] performSelector:@selector(setOrientation:)
withObject:(id)UIInterfaceOrientationLandscapeRight];
}

 

总结:如果第一种办法可以满足你需要的话,最好使用第一种办法,因为那个上 App Store肯定没问问题,但是第二种的话是需要冒风险的,但是如果你的结构太复杂了,导致使用第一种办法人为很难控制的话,可以尝试简单的使用第二种办法。

}

- (void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

[self printFrame:self.view.frame name:@"willAppearFrame"];

}

-(void)viewDidAppear:(BOOL)animated

{

[super viewDidAppear:animated];

[self printFrame:self.view.frame name:@"didappearFrame"];

}

这段代码在init方法中设置了一下view的backgroundColor。运行结果很正常,view的背景色被成功地设定为黄色,但是看控制台的log输出,出现了一个不符合预期的现象:

didloadFrame :(0.000000, 20.000000, 768.000000, 1004.000000)

initFrame :(0.000000, 20.000000, 768.000000, 1004.000000)

willAppearFrame :(0.000000, 0.000000, 768.000000, 960.000000)

didappearFrame :(0.000000, 0.000000, 768.000000, 960.000000)

willLayoutFrame :(0.000000, 0.000000, 768.000000, 960.000000)

didLayoutFrame :(0.000000, 0.000000, 768.000000, 960.000000)

viewDidLoad竟然先于init给出了输出,经过跟踪发现,原来当程序第一次调用self.view的时候,viewDidLoad方法就会被执行,而不一定非要等到init之后willAppear之前。这给我们敲响了警钟,这样的代码就隐藏了问题:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

{

self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

if (self) {

// Custom initialization

self.view.backgroundColor = [UIColor yellowColor];

aInstanceVariable_= 0; // Custom initialization of an instance variable

}

return self;

}

- (void)viewDidLoad

{

[super viewDidLoad];

// Do any additional setup after loading the view.

aInstanceVariable_ = 10086;

}

这段代码执行完后的aInstanceVariable_是0而不是10086,可能会为一些bug深深地埋下一颗种子。

搞清楚了代码执行顺序,下面我们来关注一下frame和bounds的问题。frame和bounds的定义和区别在这篇blog里讲的很清楚,总结起来要点就是,frame是相对于父view参照系(是父view而不是父viewController)的,bounds是本地参照系,改frame的时候center和bounds联动,但改bounds的时候center不动。

把上面的程序稍微修改一下,来看一组值得研究一下的结果(此viewController由带导航条的navigationController推送),实际上不用navigationController而直接加载这个vc,结果又不一样,viewDidAppear会在最后viewDidLayoutSubviews之后才调用,其他顺序不变,乱吧……

didLoadFrame :(0.000000, 20.000000, 768.000000, 1004.000000) direction:(1, 1)

didLoadBounds :(0.000000, 0.000000, 768.000000, 1004.000000) direction:(1, 1)

initFrame :(0.000000, 20.000000, 768.000000, 1004.000000) direction:(1, 1)

initBounds :(0.000000, 0.000000, 768.000000, 1004.000000) direction:(1, 1)

willAppearFrame :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

willAppearBounds :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

didAppearFrame :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

didAppearBounds :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

willLayoutFrame :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

willLayoutBounds :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

didLayoutFrame :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

didLayoutBounds :(0.000000, 0.000000, 768.000000, 960.000000) direction:(1, 1)

刚才这个是竖屏的,再来个横屏的:

didLoadFrame :(0.000000, 0.000000, 748.000000, 1024.000000) direction:(3, 3)

didLoadBounds :(0.000000, 0.000000, 748.000000, 1024.000000) direction:(3, 3)

initFrame :(0.000000, 0.000000, 748.000000, 1024.000000) direction:(3, 3)

initBounds :(0.000000, 0.000000, 748.000000, 1024.000000) direction:(3, 3)

willAppearFrame :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

willAppearBounds :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

didAppearFrame :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

didAppearBounds :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

willLayoutFrame :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

willLayoutBounds :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

didLayoutFrame :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

didLayoutBounds :(0.000000, 0.000000, 1024.000000, 704.000000) direction:(3, 3)

总结一下不难发现其特征:1. 在viewWillAppear之前,无论横屏还是竖屏,view的frame和bounds都是按竖屏方式计算的;2. 在viewWillAppear之前,navigationController(而非父view,实际上这个vc的superview是navigationController的view的一个subview)的导航条并没有计算在frame和bounds中,但电池条的宽度是一直计算了的;3. 在转屏时,触发的是viewWillLayoutSubview及viewDidLayoutSubview(data not shown)。

由此结论,我们继续往下想,如果我们要改变self.view的frame值,我们应当在哪个方法中修改呢?很容易想到的是,init和viewDidLoad中是不行的,实践证明,在viewWillAppear中也是不行的,要在viewDidAppear/viewWillLayoutSubviews/viewDidLayoutSubviews方法中修改才能产生效果。

看起来越来越复杂了……对了,以上的结论对iOS5和6是通用的。下面开始研究转屏,转屏对iOS5和6来说,差别就大了。

先看iOS5

iOS5的时候,转屏函数主要是这几个:(补:其实还有一个willAnimationRotationToInterfaceOrientation:duration:,调用时机在viewDidLayoutSubviews之后,didRotation之前)

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation

{

NSLog(@"shouldRotate");

return YES;

} //以下简称shouldRotate

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

{

NSLog(@"willRotate");

}

-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

{

NSLog(@"didRotate");

}

初始化一个正常viewController时转屏函数的调用过程如下:

2012-11-18 16:40:58.090 testRotation[1874:c07] init

2012-11-18 16:40:58.091 testRotation[1874:c07] shouldRotate

2012-11-18 16:40:58.092 testRotation[1874:c07] didLoad

2012-11-18 16:40:58.092 testRotation[1874:c07] shouldRotate

2012-11-18 16:40:58.093 testRotation[1874:c07] willappear

2012-11-18 16:40:58.093 testRotation[1874:c07] shouldRotate

2012-11-18 16:40:58.094 testRotation[1874:c07] willlayout

2012-11-18 16:40:58.095 testRotation[1874:c07] didlayout

2012-11-18 16:40:58.096 testRotation[1874:c07] didappear

我的妈呀,初始化一个vc怎么调用了三次shouldRotate方法……(别着急,三次算什么,这种情况下调用几次都有可能……)

如果初始化vc是在一个navigationController下,看起来还比较正常:

2012-11-19 20:42:42.037 testRotation[462:c07] init

2012-11-19 20:42:42.039 testRotation[462:c07] didload

2012-11-19 20:42:42.040 testRotation[462:c07] willappear

2012-11-19 20:42:42.041 testRotation[462:c07] shouldRotate

2012-11-19 20:42:42.042 testRotation[462:c07] didappear

2012-11-19 20:42:42.042 testRotation[462:c07] willlayout

2012-11-19 20:42:42.043 testRotation[462:c07] didlayout

shouldRotate在willAppear之后调用一次。

无论有navigationController与否,再转一下屏后,方法调用过程是一样的:

2012-11-19 20:51:00.729 testRotation[527:c07] shouldRotate

2012-11-19 20:51:00.730 testRotation[527:c07] willRotate

2012-11-19 20:51:00.731 testRotation[527:c07] willlayout

2012-11-19 20:51:00.731 testRotation[527:c07] didlayout

2012-11-19 20:51:00.732 testRotation[527:c07] shouldRotate

2012-11-19 20:51:01.133 testRotation[527:c07] didRotate

注意,shouldRotate方法依然被调用了两次。

为了把shouldRotate方法的调用次数以及这几次调用的返回值有什么用搞明白,我做了个实验,详细过程不赘述,只说结论。结论是一个坏消息和一个好消息:坏消息是,shouldRotate方法可能调用很多次(只出现在非navigationController方式直接将vc作为rootViewController的情况),我最多遇到过连续调用6次的,弄的我一头雾水,具体原因尚不详;好消息是,无论在哪个阶段调用多少次,起决定作用的只有willAppear调用后,willLayoutSubviews调用前shouldRotate的最后一次调用,其余阶段返回yes还是no都不重要。

再看iOS6

iOS6对转屏逻辑做了修改,去掉了原来的shouldRotate方法,代之以新的几个方法,具体可看这篇blog,介绍很详细,不再赘述,做一些补充:

-(BOOL)shouldAutorotate

{

return YES;

}

-(NSUInteger)supportedInterfaceOrientations

{

return UIInterfaceOrientationMaskAll;

}

-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation

{

return UIInterfaceOrientationLandscapeRight;

}

这3个方法代替了原来的shouldRotate方法,但并不是换汤不换药。

iOS6把转屏的逻辑判断放到了rootViewController里,也就是说,无论当前显示的是那个子vc,都是rootViewController响应转屏事件(或者present出来的modalViewController也可以,总之是最根部的vc才行),而且不向下传递。直接在一个childViewController里写这几个方法,是根本不会被调用到的。这就带来一个问题,根vc的转屏逻辑直接决定了子vc的转屏逻辑,如果老子说这个方向支持,那儿子只好支持,而且所有的儿子还都得支持。解决这个专制问题,可以在根vc里这样写:

-(BOOL)shouldAutorotate

{

return [self.topViewController shouldAutorotate];

}

-(NSUInteger)supportedInterfaceOrientations

{

return [self.topViewController supportedInterfaceOrientations];

}

让老子每次转屏被问到的时候,都亲自问下他现在正在活跃的子孙。

转屏时调用顺序跟iOS5一样,不过shouldRotate被顺序拆分为shouldAutoRotate和supported。并且如果shouldAutoRotate返回了NO,则转屏过程中断,不再继续执行supported。

最后说到强制横屏。

iOS5和6都有这个问题,如果我们采用presentViewController的方式展示一个vc,那么我们是可以在进入vc的时候控制present的方向的。但是如果我们采用的是pushViewController的方式,问题就出现了,无论我们用何种方式设置这个vc支持的屏幕方向,都只能在转屏的时候进行调整,而无法在第一次进入这个vc的时候调整。也就是说,竖屏push进入一个只支持横屏的vc,显示依然是竖屏,但当转横屏之后,就转不回竖屏了。

这显然不对,解决这个问题,要么用私有API setOrientation: 这个显然是风险太大的。比较好的解决方式就是检测屏幕方向,然后用view.transform去人工转view,setStatusBarOrientation。这里面要注意几个要点:

  1. view.transform的makeRotation方式转view是中心点center不动,view旋转。

2. 旋转过后view的frame会改变,所以要人工调整,这里计算frame的新位置和尺寸是重点。由于是人工转屏,改变电池条的方向并不会改变view的坐标系,所以一切要在原坐标系里算。

  1. view转屏退出后要记得用identity恢复之前view转过的状态。

4. 最坑爹的一点是,用setStatusBarOrientation:animated:方法来设置电池条方向时,在iOS5下没有问题,但在iOS6下,这个方法会调用rootvc的shouldAutoRotate(相当于一次转屏判断),如果shouldAutoRotate返回YES(无论supported返回什么),电池条方向都不会被设定!非常坑,所以逻辑要想好,比如可以通过一个bool值判断是在改变电池条方向还是系统转屏,如果是前者,返回个NO骗骗它……

5. 在哪个方法里处理转屏,设置电池条方向,以及在哪个方法里调整view的frame,都是很重要的,要视你的view是怎么push进来的(有rootvc还是本身就是),要具体情况具体分析。中心思想是:比如强制要求横屏,则在横屏进入的时候,直接用系统转屏逻辑限制方向即可;而在竖屏进入时,禁用系统转屏逻辑,人工将view旋转至需要的方向,而后再转为横屏时,可采用两种方式,一是恢复原本view方向后重新开启系统转屏逻辑,二是继续根据方向人工转屏。设计这个过程代码时,明确之前研究的frame尺寸应该什么时候重设以及各个view方法的执行顺序,是必须的。

5. iOS5和6要区分处理。总之,强制横屏绝对不是网上随处可见的transform一下然后重设一下bound就ok了的事情。

附上一种强制横屏实现的代码:

// 强制横屏的一种实现

// 使用方法:

// 在vc的init方法中调用initLogic

// 在vc关闭之前调用cleanRotateTrace方法

-(void)initLogic

{

isPortraitIn_ = NO;

isSettingStatusBar_ = NO;

}

计算机编程,-(BOOL)shouldAutorotate

{

if (isSettingStatusBar_)

{

return NO;

}

return YES;

}

-(NSUInteger)supportedInterfaceOrientations

{

return UIInterfaceOrientationMaskLandscape;

}

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation

{

return ((toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)||(toInterfaceOrientation == UIInterfaceOrientationLandscapeRight));

}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

{

if (isPortraitIn_)

{

self.view.transform = CGAffineTransformIdentity;

isPortraitIn_ = NO;

}

}

- (void)cleanRotationTrace

{

if (isPortraitIn_)

{

self.view.transform = CGAffineTransformIdentity;

isPortraitIn_ = NO;

UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;

if (orientation == UIInterfaceOrientationLandscapeRight)

{

isSettingStatusBar_ = YES;

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];

isSettingStatusBar_ = NO;

}

else

{

isSettingStatusBar_ = YES;

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortraitUpsideDown animated:NO];

isSettingStatusBar_ = NO;

}

[self.view setFrame:CGRectMake(0, 0, self.view.frame.size.height 20, self.view.frame.size.width - 20)];

}

}

-(void)viewDidAppear:(BOOL)animated

{

[super viewDidAppear:animated];

UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;

if (UIInterfaceOrientationIsPortrait(orientation))

{

isPortraitIn_ = YES;

self.view.transform = CGAffineTransformMakeRotation(M_PI_2);

if (orientation == UIInterfaceOrientationPortrait)

{

isSettingStatusBar_ = YES;

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO];

isSettingStatusBar_ = NO;

}

else

{

isSettingStatusBar_ = YES;

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft animated:NO];

isSettingStatusBar_ = NO;

}

[self.view setFrame:CGRectMake(0, -20, self.view.frame.size.height - 20, self.view.frame.size.width 20)];

}

}

虽然强制横屏的中心思想都差不多,但具体实现方式可以有很多种,我自己写过两种,效果都差不多,代码简洁程度不同。这些实现目前我都没有解决的问题是转屏的动画,用系统逻辑的部分没有问题,但如果是竖屏进入强制横屏的,在第一次转到真正横屏的时候,电池条的转动与view的转动是不同步的,动画很难看,之后再转就又是系统转屏没有问题了。

这个动画问题我至今能够想到的唯一解决方法是完全不用系统转屏,而是所有的转屏都自己写。求更好解决方案。

到此为止。

TAG标签:
版权声明:本文由澳门新葡8455手机版发布于计算机编程,转载请注明出处:计算机编程横屏竖屏自适应计算,从ViewControlle