【iOS】论如何优雅的使用安全区来适配iPhone X屏幕

简述

一般人而言,对屏幕的适配仅仅只是机型的适配,不会考虑到iOS系统版本(iOS6到7的适配除外)与Xcode版本。对新机型也是加个判断的事,但这样子容易造成代码过多,并且对以后新增的机型适配不利(可能要重构代码等)。接下来我要通过一些例子,提供一些优雅适配系统、机型和Xcode版本的思路。

使用masonry适配

控制器内适配


首先看一段在控制器中的代码:

SettingTableView *tableView = [[SettingTableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
[[self view] addSubview:tableView];
[tableView mas_makeConstraints:^(MASConstraintMaker *make) {
    [[make trailing] leading].equalTo([self view]);
    
    MASViewAttribute *top = [self mas_topLayoutGuideBottom];
    MASViewAttribute *bottom = [self mas_bottomLayoutGuideTop];
#ifdef __IPHONE_11_0
    if (@available(iOS 11.0, *))
    {
        top = [[self view] mas_safeAreaLayoutGuideTop];
        bottom = [[self view] mas_safeAreaLayoutGuideBottom];
    }
#endif
    [make top].equalTo(top);
    [make bottom].equalTo(bottom);
}]; 

[[make trailing] leading].equalTo([self view]); // 这行不需要多说,一般而言,add到控制器的view左右都是贴边的。重点在于下面的:

MASViewAttribute *top = [self mas_topLayoutGuideBottom];
MASViewAttribute *bottom = [self mas_bottomLayoutGuideTop]; // 这里是默认适配iOS10及以下机型
#ifdef __IPHONE_11_0   // 如果有这个宏,说明Xcode版本是9开始
    if (@available(iOS 11.0, *)) // 判断iOS系统是不是11以上
    {
        // 如果是11以上,则使用安全区来适配
        top = [[self view] mas_safeAreaLayoutGuideTop];
        bottom = [[self view] mas_safeAreaLayoutGuideBottom];
    }
#endif
    [make top].equalTo(top);
    [make bottom].equalTo(bottom);

__IPHONE_11_0 这个宏只有Xcode9及以上版本才会有,如果有这个宏,说明需要支持到iOS11以上,如果没有这个宏,下面的if语句会在预编译时忽然掉,所以即使是低版本的Xcode没有下面的方法也不会报错。
另外需要注意下
mas_bottomLayoutGuide是与tabbar、toolbar等相关的,默认值是mas_bottomLayoutGuideTop
mas_bottomLayoutGuideTop是指参考到tabbar的top
mas_bottomLayoutGuideBottom是指参考到tabbar的bottom
所以,在没有显示有tabbar等系统控件的情况下,Top和Bottom都是一样的。

在自定义view内适配


在自定义view中,如果涉及到安全区和屏幕旋转(比如视频播放器里的view),我们该如何适配呢?
同样的,我们先来看段代码:

[lockButton mas_makeConstraints:^(MASConstraintMaker *make) {
    [make centerY].equalTo(self);
    [[make width] height].mas_equalTo(50);
    
    MASViewAttribute *leading = [self mas_leading];
#ifdef __IPHONE_11_0
    if (@available(iOS 11.0, *))
    {
        leading = [self mas_safeAreaLayoutGuide];
    }
#endif
    [make leading].equalTo(leading);
}];

这是播放器里面常见的小锁头按钮,这个按钮我们先让它居中,然后放在左侧的安全区内。这段代码是允许屏幕旋转的。
mas_safeAreaLayoutGuide表示在安全区内,并没有表示方向,事实上可以加Leading来表示左侧(即mas_safeAreaLayoutGuideLeading)。

使用frame布局

虽然我不提倡使用这种落后的布局方式,但有些情况下还是挺有用的,现在我们来看下如何使用frame来适配X的屏幕。

在控制器内适配


首先,我们在工具类里面增加一个类方法,我这里的工具类的名字是Tools。

+ (UIEdgeInsets)safeAreaInsetsWithView:(UIView *)view // 获取安全区域
{
#ifdef __IPHONE_11_0
    if (@available(iOS 11.0, *))
    {
        return [view safeAreaInsets];
    }
#endif

    return UIEdgeInsetsZero;
}

然后,我们来看看控制器里的方法。
如果是在某个时候出现的view(比如点击的时候才会创建并显示),我们可以使用以下代码来适配:

CGFloat height = kToolBarHeight;
CGRect frame = CGRectMake(0, self.view.frame.size.height, self.view.frame.size.width, height);
UIEdgeInsets insets = [Tools safeAreaInsetsWithView:[self view]];
frame.origin.y += insets.bottom;
frame.size.height += insets.bottom;
UIView *view = [[UIView alloc] initWithFrame:frame];

但是,如果是需要进来就显示的view,就不能用上面的方法去适配了,因为safeAreaInsets直到viewSafeAreaInsetsDidChange调用前,都是UIEdgeInsetsZero。
viewSafeAreaInsetsDidChange的调用在viewDidLayoutSubviews之前,所以如果我们需要进来就布局好的话,可以在viewDidLayoutSubviews里布局。
但是,viewDidLayoutSubviews的调用是很频繁的,如果你在viewDidLoad已经布局好,只想当安全区改变的时候去适配安全区,那就应该重写viewSafeAreaInsetsDidChange方法,在viewSafeAreaInsetsDidChange里适配,而不是在viewDidLayoutSubviews里适配。

#ifdef __IPHONE_11_0
- (void)viewSafeAreaInsetsDidChange
{
    [super viewSafeAreaInsetsDidChange];
    
    // 高度增加到最底部
    CGRect frame = [[self bottomView] frame];
    UIEdgeInsets insets = [Tools safeAreaInsetsWithView:[self view]];
    CGFloat height = kToolBarHeight;
    height += insets.bottom;
    frame.size.height = height;
    [[self bottomView] setFrame:frame];

    return;
}
#endif  

事实上这里的safeAreaInsetsWithView方法调用可以换成直接使用[[self view] safeAreaInsets],但是为了以后考虑(鬼知道苹果之后会出什么奇葩操作。。。),统一使用该方法来获取安全区,如果以后需要修改,我们只需要修改safeAreaInsetsWithView方法的实现即可。

在自定义view内适配


自定义view里其实和控制器是差不多的,只需要把viewSafeAreaInsetsDidChange换成safeAreaInsetsDidChange即可:

#ifdef __IPHONE_11_0
- (void)safeAreaInsetsDidChange
{
    [super safeAreaInsetsDidChange];
    
    // 把y调到安全区内
    CGRect frame = [[self indicator] frame];
    UIEdgeInsets insets = [Tools safeAreaInsetsWithView:self];
    frame.origin.y = insets.top;
    [[self indicator] setFrame:frame];
    
    return;
}
#endif  

此外,你可能会使用到以下的宏,我一般把它们定义在pch文件里:

#define kScreenHeight [[UIScreen mainScreen] bounds].size.height // 物理屏幕高度
#define kScreenWidth [[UIScreen mainScreen] bounds].size.width   // 物理屏幕宽度
#define kIsFullScreen ((([[[UIDevice currentDevice] systemVersion] floatValue] >= 11.0f) && ([[[[UIApplication sharedApplication] delegate] window] safeAreaInsets].bottom > 0.0))? YES : NO) // 判断是否全面屏
#define kIsiPhoneX CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) // 判断是否是iPhone X
#define kStatusBarHeight (kIsFullScreen ? 44.f : 20.f)       // 状态栏高度
#define kNavigationBarHeight (kIsFullScreen ? 88.f : 64.f)   // 导航栏高度
#define kTabBarHeight (kIsFullScreen? (49.f + 34.f) : 49.f)  // tabBar高度
#define kHomeIndicatorHeight (kIsFullScreen ? 34.f : 0.f)    // home指示器高度 

结语

简洁优雅的代码都是大家努力追求的,平时留点心就能为维护带来想不到的好处,何乐而不为呢?好了,抛砖引玉就到此结束了,有什么好的建议与不足可以在评论中指出,我会根据实际情况来更新,谢谢大家的阅读。

最后,感谢我女朋友在我饿着肚子写文章的时候,给我买了我喜欢吃的

iOS OC Swift Flutter开发群 139322447

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 原文地址:快速自动化适配iPhone X 关于iPhone X的适配,主要需要做的工作点就是针对上下非安全区域的适...
    默默_David阅读 1,272评论 0 4
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,510评论 25 707
  • 最近看了许多iPhone X适配的文章,发现很少有介绍safeArea的,就来随便写写 现在对于iPhone X的...
    fruitymoon阅读 105,486评论 59 261
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,693评论 2 59
  • 饮中八仙包括李白、贺知章、李适之、李琎、崔宗之、苏晋、张旭、焦遂。 指唐朝嗜酒好仙的八位学者名人,亦称酒中八仙或醉...
    天马酒仙阅读 1,222评论 0 0