跳过正文
  1. Posts/

iOS横竖屏总结

··3 分钟

2018.7.23

使用Auto Layout来进行布局就不用自己去监听横竖屏事件了,只需要绘制多套布局即可。但是项目有很多页面是自己手动计算的,于是只有想办法再旋转屏幕时重新布局。

相关枚举
#

屏幕方向有3个相关枚举,界面方向UIInterfaceOrientation,设备方向UIDeviceOrientation,支持旋转方向UIInterfaceOrientationMask

注意UIInterfaceOrientation与UIDeviceOrientation左右方向是相反的

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
};

typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
};

横竖屏控制
#

控制界面横竖屏切换有3个重要的点,最终结果以这三个地方的值取交集。

1.info.plist全局控制

可以在General->Deplyment Info界面上勾选

info.plist文件中配置也是一样的,两边会同步变更

2.AppDelegate中根据不同Window控制

// AppDelegate
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskPortrait;
}

3.在ViewController中控制当前页面

// UIViewController
- (BOOL)shouldAutorotate {
    return YES;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeLeft;
}

需要注意的是,交集不能为空,否则会导致crash

*** Terminating app due to uncaught exception ‘UIApplicationInvalidInterfaceOrientation’, reason: ‘Supported orientations has no common orientation with the application, and [ViewController shouldAutorotate] is returning YES’

旋转事件监听
#

旋转事件传递过程
#

op0=>operation: __CFRunLoopDoSources0
op1=>operation: UIDevice
op2=>operation: UIWindow
op3=>operation: UIViewController
op4=>operation: UIView

op1->op2->op3->op4

屏幕旋转相关事件
#

viewWillTransitionToSize:withTransitionCoordinator:

  • ViewController被父容器变更size时调用(例如window旋转时调用root view controller的该方法)
  • 如果重载该方法,需要调用super传递事件给子ViewController
  • 这个方法是最关键的,可以在该方法中对界面进行重新布局
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
    // coordinator用来处理转换动画
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
     {
         // 开始旋转
     } completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
     {
         // 旋转结束
     }];
     // 记得调用super
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}

UIApplicationWillChangeStatusBarOrientationNotification

  • 状态栏将要旋转,这个时候取view的frame还是旋转之前的
  • NSNotification中用key UIApplicationStatusBarOrientationUserInfoKey可以取到将要旋转到的方向。

UIApplicationDidChangeStatusBarOrientationNotification

  • 状态栏已经旋转,这个时候取view的frame是旋转之后的
  • NSNotification中用key UIApplicationStatusBarOrientationUserInfoKey可以取到旋转之前的方向。

UIDeviceOrientationDidChangeNotification

  • 设备方向变更,在收到通知时取view的frame是旋转之后的。
  • 在手机上将旋转屏幕锁定之后,设备方向变更之后收不到该通知
  • 在代码里面限制设备旋转方向,设备方向变更后依然能收到该通知

调用顺序如下

st=>start: 旋转屏幕
e=>end: 结束
op1=>operation: viewWillTransitionToSize:withTransitionCoordinator:
op2=>operation: UIApplicationWillChangeStatusBarOrientationNotification
op3=>operation: UIApplicationDidChangeStatusBarOrientationNotification
op4=>operation: viewWillLayoutSubviews
op5=>operation: viewDidLayoutSubviews
op6=>operation: UIDeviceOrientationDidChangeNotification

st->op1->op2->op3->op4->op5->op6->e

自定义Window的旋转事件
#

如果想要在自定义Window的子View收到屏幕旋转通知,要设置UIWindow的rootViewController,然后把所有子view都加到rootViewController,系统会处理横竖屏事件。这里我还遇到一个坑记一个实现UIWindow子类的小坑


推荐阅读
#

https://satanwoo.github.io/2016/09/17/uiwindow-iOS/ iOS屏幕旋转知识点以及实现 iOS 屏幕旋转的那些事(一) 浅谈iOS的多Window处理

相关文章

记一个实现UIWindow子类的小坑

··1 分钟
2018.7.19 问题描述 # 项目中为了实现一个全局遮罩界面,使用了一个UIWindow的子类MyWindow,MyWindow为了实现回调定义了代理MyWindowDelegate。代码大致如下:

一个autoreleasepool的使用场景

··1 分钟
2018.7.18 今天在学习大佬博客的时候看到一个问题,下面代码会有什么问题? // largeNumber是一个很大的数 for (int i = 0; i < largeNumber; i++) { NSString *str = [NSString stringWithFormat:@"hello -%04d", i]; str = [str stringByAppendingString:@" - world"]; NSLog(@"%@", str); } 刚开始没看出什么问题,就是普通的循环,每次循环创建一个局部变量NSString。于是写了个Demo验证了下,在观察内存的时候发现了端倪,在循环过程中,内存不断飙升。

UITabbar自定义Badge

··1 分钟
2018.7.10 tabBarItem的Badge默认样式是带数字的,但是产品要求只要一个小红点,不需要数字,这就需要我们自定义Badge了。 用Reveal分析UITabBar,发现每个按钮是一个UITabBarButton,层级如下:

计算文字长度

··2 分钟
2018.7.9 官方文档 方法定义 # - (NSRect)boundingRectWithSize:(NSSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary<NSAttributedStringKey, id> *)attributes context:(NSStringDrawingContext *)context; 参数定义 # size # 绘制的限制size,计算出来的值不会超过这个大小。

UITextField控制输入长度

··1 分钟
2018.7.6 有些时候会有控制输入框文字长度的需求,记录一个简单的思路。 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if (string.length == 0) { return YES; } NSInteger limit = 15; // 文本的最大长度 NSString *newStr = [textField.text stringByAppendingString:string]; // 修改之后的新字符串 NSInteger newStrLength = newStr.length; newStrLength -= [textField textInRange:[textField markedTextRange]].length; // 去掉高亮内容,输入中文拼音的情况 if (newStrLength > limit) { // 处理composed character, 比如emoji NSString *tempStr = [newStr substringWithRange:[newStr rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, limit)]]; textField.text = tempStr; return NO; } return YES; } 有两个坑注意下: