美文网首页自动布局iOS开发分享
【iOS】论如何优雅的使用安全区来适配iPhone X屏幕

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

作者: 子天々君 | 来源:发表于2018-09-23 17:48 被阅读386次

    简述

    一般人而言,对屏幕的适配仅仅只是机型的适配,不会考虑到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指示器高度 
    

    结语

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

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

    相关文章

      网友评论

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

      本文链接:https://www.haomeiwen.com/subject/bjtqoftx.html