UIViewController中配置旋转
- - (BOOL)shouldAutorotate: 标志VC是否可以自动旋转
- - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation: 当 shouldAutorotate 返回YES时有效,标志所支持的方向(UIInterfaceOrientationMask).当用户改变设备的方向时,系统会调用 root View 或最顶层(使用present出来的mode界面)中的supportedInterfaceOrientations函数.
- - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation: 优先的方向
- + (void)attemptRotationToDeviceOrientation: 尝试使用设备的方向旋转让所有的窗口
在用户旋转设备(手机)时,最顶层的VC要触发这些函数,有以下的情况:
- vc是用 presentViewController 出来的,那vc就是一个顶层窗口,自然就能触发相关函数了
- vc如果是UINavigationController的topViewController,那只有UINavigationController本身会触发到这些函数,因为UINavigationController就是一个UIViewController,所以一般会继承UINavigationController,并实现相关函数做处理,如下代码所示:
#pragma mark - rotation override
- (BOOL)shouldAutorotate
{
if ([self.topViewController respondsToSelector:@selector(shouldAutorotate)])
{
return [self.topViewController shouldAutorotate];
}
return [super shouldAutorotate];
}
#ifdef __IPHONE_9_0
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
#else
- (NSUInteger)supportedInterfaceOrientations
#endif
{
if ([self.topViewController respondsToSelector:@selector(supportedInterfaceOrientations)])
{
return [self.topViewController supportedInterfaceOrientations];
}
return [super supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
if ([self.topViewController respondsToSelector:@selector(preferredInterfaceOrientationForPresentation)])
{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
return [super preferredInterfaceOrientationForPresentation];
}
关于旋转相关的几个消息
- UIDeviceOrientationDidChangeNotification: 只要设备旋转,则注册的对象会接收到此消息,不管是否禁止旋转
- UIApplicationWillChangeStatusBarOrientationNotification与UIApplicationDidChangeStatusBarOrientationNotification: 若禁止旋转,则不会接收到此消息
强制屏幕方向(横屏,竖屏)
实现方式:
- 对 UIDevice 的 orientation 进行处理.这是一个苹果3.0之后就不太愿意支持的方式,但对于码农来说是最方便的, 实现代码如下,有两种
//实现方式1:
- (void)toOrientation:(UIInterfaceOrientation)aOrientation
{
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)])
{
mIsCanAutorotate = YES;
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = aOrientation;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
mIsCanAutorotate = NO;
}
}
//实现方式2
- (void)toOrientation:(UIInterfaceOrientation)aOrientation
{
//[[UIDevice currentDevice] setValue:@(UIInterfaceOrientationUnknown) forKey:@"orientation"]; //若想强制触发,或加上此句
[[UIDevice currentDevice] setValue:@(aOrientation) forKey:@"orientation"];
}
若想进入VC就直接切换方式, 则需要在 viewDidLoad或viewWillAppear调用 toOrientation 在退出时还原原来的屏幕方向.需要在pop
- 第二种方式比较麻烦,你需要对状态栏及View进行旋转,比如以下代码:
/*旋转方向: UIInterfaceOrientationLandscapeLeft*/
UIViewController *vc = self.navigationController;
vc.view.transform = CGAffineTransformMakeRotation(-M_PI_2);
vc.view.frame = [UIScreen mainScreen].bounds;
//此时针对旋转的VC的 shouldAutorotate 需要返回NO, 这样setStatusBarOrientation才有效
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft animated:YES];
/*旋转方向: UIInterfaceOrientationPortrait*/
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:YES];
UIViewController *vc = self.navigationController;
vc.view.transform = CGAffineTransformIdentity;
CGRect frame = [UIScreen mainScreen].bounds;
vc.view.frame = frame;
注意: 代码调用的位置与得到的结果有很大的关系,首先是 setStatusBarOrientation, 它针对当前VC的 shouldAutorotate 返回值来确定是否可以执行. 旋转的是整个界面,当然包括状态栏了. 比如你在 viewWillDisappear 中调用.那么 setStatusBarOrientation 针对的VC就是 UINavigationController的topViewControll(topVC),这时你就得让这个topViewControl的shouldAutorotate返回NO.当前VC(curVC) 不是 topVC, 这样一来 curVC 与 topVC 的关联就太深了.当然,这是个小问题,我们可以用扩展类或派生类来处理,我在JxCommon库中是这实现的:
//UIViewController(JxCommon)头文件
typedef NS_ENUM(NSUInteger, TAutorotateType)
{
atDonotAutorotate,
atCanAutorotate,
atChildHandle //针对容器UINavigationController的topViewController或UITabBarController的selectedViewController,其它情况返回YES
};
//UIViewController(JxCommon)实现文件
#pragma mark - statusBar
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (BOOL)shouldAutorotate
{
switch (self.autorotateType)
{
case atCanAutorotate: return YES;
case atDonotAutorotate: return NO;
default:
break;
}
//atChildHandle
UIViewController *vc;
if ([self isKindOfClass:[UINavigationController class]])
{
vc = ((UINavigationController *)self).topViewController;
}
else if ([self isKindOfClass:[UITabBarController class]])
{
vc = ((UITabBarController *)self).selectedViewController;
}
if (vc && [vc respondsToSelector:@selector(shouldAutorotate)])
{
return [vc shouldAutorotate];
}
return YES;
}
#pragma clang diagnostic pop
这样在使用旋转时,代码如下:
- (void)testTransform
{
UIViewController *vc = self.navigationController;
UIViewController *topVC = self.navigationController.topViewController;
TAutorotateType oldType = vc.autorotateType;
vc.autorotateType = atDonotAutorotate;
[UIView animateWithDuration:0.25 animations:^{
if (aOrientation == UIInterfaceOrientationPortrait)
{
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:YES];
vc.view.transform = CGAffineTransformIdentity;
CGRect frame = [UIScreen mainScreen].bounds;
vc.view.frame = frame;
//计算 navigationBar 的位置
CGRect r = topVC.navigationController.navigationBar.frame;
r.origin.y = topVC.statusBarHidden ? 0 : 20;
r.size.height = 44;
topVC.navigationController.navigationBar.frame = r;
logoutRect(r);
}
else
{
UIViewController *vc = self.navigationController;
vc.view.transform = CGAffineTransformMakeRotation(-M_PI_2);
vc.view.frame = [UIScreen mainScreen].bounds;
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft animated:YES];
}
} completion:^(BOOL finished) {
vc.autorotateType = oldType;
}];
}
这是一部分代码,大体没问题了,接下来是: 显示或隐藏状态栏,用下面的代码:
- (void)changedStatusBarVisible
{
//必须修改navigationBar的位置,不能旋转后不能自动修改高度,这也是日了狗
self.statusBarHidden = !self.statusBarHidden;
CGRect r = self.navigationController.navigationBar.frame;
r.size.height = 24 + (self.statusBarHidden ? 0 : 20);
[UIView animateWithDuration:0.25 animations:^{
[self setNeedsStatusBarAppearanceUpdate];
self.navigationController.navigationBar.frame = r;
[self.navigationController.view layoutIfNeeded];
}];
}
在iOS9以上是没问题了,切换到iOS8.发现不能点击了,好吧,不想再研究下去了.本来一句代码的问题,使用 transform 这种方式,差点要了老命.特别是还不兼容..这也是日了狗的节奏.还是老实用 orientation 的方式来做简单
结束!