iOS

[iOS] iOS布局适配

2019-10-04  本文已影响0人  木小易Ying

这篇文章真的是拖拖拖拖拖了好久,因为自己也没有什么成熟的经验,在看了超多文章以后大概总结一下,心里非常虚吖。。


#define kIS_IPHONE_3_5_INCH (kSCREEN_WIDTH == 320.f && kSCREEN_HEIGHT == 480.0f)//4(S) / 3GS
#define kIS_IPHONE_4_0_INCH (kSCREEN_WIDTH == 320.f && kSCREEN_HEIGHT == 568.0f)//5(C)/ 5(S)
#define kIS_IPHONE_4_7_INCH (kSCREEN_WIDTH == 375.f && kSCREEN_HEIGHT == 667.0f)//6(S) / 7 / 8
#define kIS_IPHONE_5_5_INCH (kSCREEN_WIDTH == 414.f && kSCREEN_HEIGHT == 736.0f)//6(S)+ / 7+ / 8+
#define kIS_IPHONE_5_15_INCH (kSCREEN_WIDTH == 375.f && kSCREEN_HEIGHT == 812.f)//X XS
#define kIS_IPHONE_6_1_INCH (kSCREEN_WIDTH == 414.f && kSCREEN_HEIGHT == 896.f)//XR XSMax

iOS最开始的布局定位都是靠frame,也就是坐标和宽高(x.y.heiight.width),任何一个view你都得固定写死它的frame,然而这样的话,当屏幕尺寸多种多样的时候就非常难过了,于是出现了AutoLayout,所以先看两个背景知识吧~

Base1: AutoLayout

参考:https://www.jianshu.com/p/4ae8457d14b0

简单的说,autolayout将原来的固定frame改为了用相对位置来计算,我们现在经常用到constraint就属于autolayout。

举个栗子哈,如果定位一个按钮距离屏幕两侧都是32点,那么只要设定:

button.leading = superview.leading + 32
button.trailing = superview.trailing + 32

如果没有autolayout,那么我们就需要每种屏幕都用以下的方式定位:

if (kIS_IPHONE_4_7_INCH) {
  button.frame = CGRectMake(32, 0, 375 - 32 - 32, 48); // y坐标和height是随便写的
} else if (kIS_IPHONE_5_5_INCH) {
  button.frame = CGRectMake(32, 0, 414 - 32 - 32, 48);
} else {
  ……  
}

这么看的话autolayout其实已经帮我们解决很多问题了,所以其实约束超级有用,但是有的时候不是所有屏幕的button都距离两侧是32个点,如果希望在iphone6/7/8上面是32点,但是其他屏幕根据屏幕宽度比例进行调整,那么就涉及了屏幕适配。

例如:
iPhone 6/7/8/X/XS 宽度375 -> 32点
iPhone 6+/7+/8+/Xr/Xs max 宽度414 -> 32/375*414 = 35.328点
iPhone 4/5 宽度320 -> 32/375*320 = 27.306点

P.S. 这里强烈不建议每个屏幕的距离设定是没有比例关系并且不一致的,这样就会导致开发每种屏幕都要if然后设定constraint数值,非常的麻烦不好维护,出任何一种新的屏幕的时候都非常要命。。

Base2: SizeClass

sizeClass可以用于区分横竖屏、iPAD之类的,使用场景例如你可以建好2个constraint,一个用于横屏,一个用于竖屏,具体可参考:https://www.jianshu.com/p/0b91341fead4,这个我之前有用来做extension的横竖屏适配。


屏幕适配我看了蛮多文章的,大意上分两种方式:(字体适配另说哦)

  1. 手写布局+比例适配
  2. xib+比例适配

还有一些神奇的方法,比如借鉴了web适配的flexlib,具有热更新的优点,只是因为改起来容易冲突所以并不推荐。

比较common的是,无论怎样都是基于比例适配,也就是设计师只要给一个iphone8的(具体尺寸看各个公司的偏好了)设计稿,然后所有间距、宽高都按照比例缩放。

但是不是所有控件的尺寸都应该按照屏幕大小等比放大的哈,具体的适配理念主要是三种:文字流式、控件弹性、图片等比缩放

适配准则

所以设计师需要确定每一种控件的布局规则,要不dev做出来的可能和他们设想的不一样,导致折返跑。

首先common其实都是比例适配,下面我们来探讨一下如何做到比例适配啦~ 虽然其实很简单就是按比例计算一下上下左右应该是多少。。

#define kIPHONE8_WIDTH 375.0f
#define kIPHONE8_HEIGHT 667.0f

// 水平or竖直方向
+ (CGFloat)horizontalAdapter:(CGFloat)constant {
    return constant * ([UIScreen mainScreen].bounds.size.width / kIPHONE8_WIDTH);
}

+ (CGFloat)verticalAdapter:(CGFloat) constant {
    return constant * ([UIScreen mainScreen].bounds.size.height / kIPHONE8_HEIGHT);
}

上下有一点要注意的就是刘海屏的适配,一般可以考虑用safeArea作为屏幕尺寸,或者以全屏view作为适配尺寸后单独处理刘海。

safeArea

Step 1:首先得加入顶部和底部margin

+ (CGFloat)topAdapter:(CGFloat)top {
    if (kIS_IPHONE_5_15_INCH || kIS_IPHONE_6_1_INCH) {
        return top + 44;
    }
    return top;
}

+ (CGFloat)bottomAdapter:(CGFloat)bottom {
    if (kIS_IPHONE_5_15_INCH || kIS_IPHONE_6_1_INCH) {
        return bottom + 34;
    }
    return bottom;
}

Step 2:可选部分,是不是以不算marigin的区域作为比例缩放的标尺

如果你想获取safeArea的大小,可以用下面的方式得到,但是时机有点晚所以我会在代码里面写死宏:

float vertocalInset = self.view.safeAreaInsets.top + self.view.safeAreaInsets.bottom;
NSLog(@"vertocalInset :%f", vertocalInset);

输出  vertocalInset :78.000000

注意不是刘海屏其实也有safeArea哈,就是顶部状态栏,大概20个点,这个取决于你的app是不是有状态栏了,如果没有的话可以不管这个20点,有的话需要减去。

#define kIPHONEX_VERTICAL_INSET 78.0f
#define kIPHONE_VERTICAL_INSET 20.0f

+ (CGFloat)safeAreaVerticalAdapter:(CGFloat)constant {
    if (kIS_IPHONE_5_15_INCH || kIS_IPHONE_6_1_INCH) {
        return constant * (([UIScreen mainScreen].bounds.size.height - kIPHONEX_VERTICAL_INSET) / (kIPHONE8_HEIGHT - kIPHONE_VERTICAL_INSET));
    }
    
    return constant * ([UIScreen mainScreen].bounds.size.height / kIPHONE8_HEIGHT);
}

1. 手写布局+比例适配

手写布局有很多好处,例如性能更好,但是没有xib看起来明了,所以写的时候可能只是稍微有点痛苦,布局一旦复杂起来,后面改的人就是相当痛苦。。

其实关键还是比例适配,手写布局的实现太多啦,正常的话就像下面这样写就可以啦:

UIView *redView = [[UIView alloc] initWithFrame:CGRectMake([AdapterUtils horizontalAdapter:20], [AdapterUtils topAdapter:[AdapterUtils safeAreaVerticalAdapter:20]], [AdapterUtils horizontalAdapter:40], [AdapterUtils safeAreaVerticalAdapter:40])];
    
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];

这样是不是看起来有一点麻烦?也可以将CGRectMake写成一个util的函数,然后每次只要调用自己定义的CGRectMake就可以啦。

CG_INLINE CGRect
CGRectMakeAdapater(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
    CGRect rect;
    rect.origin.x = [AdapterUtils horizontalAdapter:x];
    rect.origin.y = [AdapterUtils topAdapter:[AdapterUtils safeAreaVerticalAdapter:y]];
    rect.size.width = [AdapterUtils horizontalAdapter:width];
    rect.size.height = [AdapterUtils safeAreaVerticalAdapter:height];
    return rect;
}

但是有的时候可能并不是上下左右都是按比例的哈,大部分其实长宽比应该保持不变的。

效果就是酱紫啦:(左:Xr 中:8 右:5)


按比例适配前 按比例适配后

比较明显的可以看出来如果不通过屏幕比例缩放的适配前的图中,40*40的方块在iphone5上面过大,在iphone Xr上面过小。

2. xib布局+比例适配

通过xib直接做其实就是通过category以及runtime动态在运行时设置adapt后的constraint。

#import <objc/runtime.h>
#import "NSLayoutConstraint+Adapter.h"
#import "AdapterUtils.h"

@implementation NSLayoutConstraint (Adapter)

- (void)setHorizontalAdapter:(BOOL)horizontalAdapter {
    if (horizontalAdapter) {
        self.constant = [AdapterUtils horizontalAdapter:self.constant];
    }
    objc_setAssociatedObject(self, @selector(horizontalAdapter), @(horizontalAdapter), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)horizontalAdapter {
    NSNumber *value = objc_getAssociatedObject(self, @selector(horizontalAdapter));
    return value.boolValue;
}

- (void)setVerticalAdapter:(BOOL)verticalAdapter {
    if (verticalAdapter) {
        self.constant = [AdapterUtils verticalAdapter:self.constant];
    }
    objc_setAssociatedObject(self, @selector(verticalAdapter), @(verticalAdapter), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)verticalAdapter {
    NSNumber *value = objc_getAssociatedObject(self, @selector(verticalAdapter));
    return value.boolValue;
}

- (void)setTopAdapter:(BOOL)topAdapter {
    if (topAdapter) {
        self.constant = [AdapterUtils topAdapter:self.constant];
    }
    objc_setAssociatedObject(self, @selector(topAdapter), @(topAdapter), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)topAdapter {
    NSNumber *value = objc_getAssociatedObject(self, @selector(topAdapter));
    return value.boolValue;
}

- (void)setBottomAdapter:(BOOL)bottomAdapter {
    if (bottomAdapter) {
        self.constant = [AdapterUtils bottomAdapter:self.constant];
    }
    objc_setAssociatedObject(self, @selector(bottomAdapter), @(bottomAdapter), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)bottomAdapter {
    NSNumber *value = objc_getAssociatedObject(self, @selector(bottomAdapter));
    return value.boolValue;
}

当我们把新写的category引入以后,在布局界面就能看到下图这样的选项:(右上)


约束

适配后的效果如下:


适配效果(通过约束)

字体适配

其实原理和view的很像,如果直接按屏幕的话可以像下面酱紫:

+ (CGFloat)fontSizeAdapter:(CGFloat)fontSize {
    return fontSize * ([UIScreen mainScreen].bounds.size.width / kIPHONE8_WIDTH);
}

但我们设计师的需求一般都是se减2号,Xr和Max系列加1号,这种情况就要判断一下啦~

+ (CGFloat)fontSizeAdapter:(CGFloat)fontSize {
    if (kIS_IPHONE_6_1_INCH || kIS_IPHONE_5_15_INCH) {
        return fontSize + 1;
    }
    
    if (kIS_IPHONE_4_0_INCH || kIS_IPHONE_3_5_INCH) {
        return fontSize - 2;
    }
    
    return fontSize;
}

还可以自定义fontWithName,这样每次用的时候用自己自定义的比较方便。


其实还有一个最最最傻的方式,也是我们之前用的方式,如果有哪些屏幕特别别扭,那么就特殊判断一下然后改constant,但及其不推荐啦。屏幕适配其实还是以设计师为主的,他们需要指出每个屏幕的适配准则,是比例还是固定宽高比,亦或是固定距离无论屏幕多大。

如果大家有好的方法欢迎私信或留言,非常感激!

参考:

  1. autolayout:https://www.jianshu.com/p/efb2cc3b5f03
  2. 屏幕适配:https://www.jianshu.com/p/e0775043af3e
  3. 基于xib的适配库:https://github.com/zhenglibao/FlexLib
  4. 适配:https://mp.weixin.qq.com/s?__biz=MzA4NTQzNTMwOA==&mid=201174413&idx=3&sn=c3fe5b3459bac288e8ecafc9fb038a1d&scene=2&from=timeline&isappinstalled=0#rd
上一篇 下一篇

猜你喜欢

热点阅读