iOS屏幕适配

最近准备学习OC,先简单地搜索一下屏幕适配,发现没有特别省事的适配方法,决定按照android适配的方法简单地写一个适配方案。希望有人可以受到启发,写出一套懒人适配方案,还请记得告诉我一下,谢谢。另外,由于接触OC不久,所以很多常识性的东西都还不懂,请见谅。

来到正文,android手机的屏幕千奇百怪,推荐鸿洋大神的百分比自动适配方案:
http://blog.csdn.net/lmj623565791/article/details/49990941
也有其他大神精简的方案:
http://blog.csdn.net/zhengjingle/article/details/51742839
可以根据自己的需求适当改写。

核心原理

原则上来说知道了控件宽度和高度以及左边距和上边距就可以确定一个控件。
所以我们只需要根据设计图的宽高与实际屏幕宽高比例对各种控件的宽度、高度、上下左右边距进行缩放就可以完成初步布局,再加上字体、内边距、圆角大小等就可以完美实现布局效果。

目前我选取的方式是使用storyboard拖拽控件布局,因为拖拽很快。

首先假设我们的设计图是750x1334,然后我们根据设计图拖拽控件,设置好控件的宽度和高度,然后设置依赖关系即可。上面提到我们只需要设置宽度、高度、左边距和上边距就可以确定一个控件。我们直接往默认的ViewController对应的main.storyboard上拖拽一个UIButton。设置宽度为200,高度100,距离屏幕左边40,屏幕顶部50。

首先获取屏幕宽度与高度,6s的屏幕尺寸也是750x1334,在6s上的宽度缩放比例wRate =750 /750=1,高度缩放比例hRate=1334/1334=1。SE的屏幕尺寸是640x1138,宽度缩放比例wRate=640/750,高度缩放比例hRate=1138/1334。

高度和宽度

我们首先处理View的宽度与高度。每一个view都有宽度与高度,如果明确指定了宽度与高度的约束,那么我们就可以拿到宽度与高度的值;如果没有指定,我们拿不到宽度与高度的值,那么我们不需要处理,系统会处理好。

比如我们指定一个buttonA,宽度50,高度60,左边距40,上边距50,那么我们就可以通过NSLayoutAttributeWidth和NSLayoutAttributeHeight约束拿到50和60。
我们在指定buttonB,高度50,上边距50,左边距50,右边距50,我们可以通过NSLayoutAttributeHeight拿到高度50,我们只要改变左边距和右边距的值,系统会自动处理宽度。

 //所有的view都需要适配宽高
NSArray* constrains = view.constraints;
for (NSLayoutConstraint* constraint in constrains) {
    CGFloat temp =constraint.constant;        
    if(temp!=0){
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            printf("适配前高度的约束--%f\n",constraint.constant);
            constraint.constant  = temp * hRate;
            printf("适配后高度的约束--%f\n",constraint.constant);
        }else if(constraint.firstAttribute == NSLayoutAttributeWidth){
            printf("适配前宽度的约束--%f\n",constraint.constant);
            constraint.constant  = temp * wRate;
            printf("适配后宽度的约束--%f\n",constraint.constant);
        }
    }
}

上下左右边距

然后处理VIew的上下左右边距,我们把所有的边距处理都交给父View,在父View里可以得到所有的依赖关系。

NSArray* constrains = view.constraints;
for (NSLayoutConstraint* constraint in constrains) {
    CGFloat temp=constraint.constant;
    if(temp!=0){
        //这里只需要判断firstAttribute即可
        //因为leading只可能跟leading或者trailing有关系,都是水平的
        if (constraint.firstAttribute == NSLayoutAttributeLeading
            || constraint.firstAttribute == NSLayoutAttributeTrailing) {
            printf("find width\n");
            constraint.constant = temp * wRate;//左右水平缩放
        }else if(constraint.firstAttribute ==NSLayoutAttributeTop
                 ||constraint.firstAttribute == NSLayoutAttributeBottom){
                   printf("find heihgt\n");
            constraint.constant  = temp * hRate;//上下竖直缩放
        }
    }
}

通过处理View的宽度、高度、上下左右边距就可以确定View。当然绘制View的时候系统可能会对小数点进行截断,比如button缩放之后的宽度可能是200.66666667,系统只绘制了200.5,如果布局要求特别精准的话需要特别处理。

从1到100

上面我们已经可以确定一个View的位置以及尺寸,接下来我们要做的就是确定每一个View的位置以及尺寸。
我们首先判断一个view是否有子view ,如果没有子View,那么很简单,只需要管好自己就好,直接宽高按比例缩放即可;如果有子View,那么不仅需要对自身宽高进行缩放,还需要缩放子View之间的margin以及子View与父View之间的上下左右边距,然后处理每一个子View。一直递归,直到全部处理完毕。

我们新建一个工程,默认的是ViewController.m。我们拖拽一个UIButton到main.storyboard,设置宽度200,高度100,左边距40,上边距50,传入viewController.view,首先缩放viewController.view的宽度与高度。然后缩放viewController.view与UIButton的依赖。
处理viewController.view之后,处理UIButton,先处理UIButton的宽度与高度,由于UIButton没有子View,就不需要再处理对应的子View之间的依赖关系以及父子View之间的对应关系。
如果在6s上,最后UIButton的宽度是200 * wRate=200 * 1=200,高度是100* hRate=100 * 1=100,左边距是40*wRate =40*1 =40,上边距是50 * hRate=50*1=50。
如果在SE上,最后UIButton宽度是200 * wRate=200*640/750, 高度是100 * hRate=100 * 1138/1334,左边距是40 * wRate= 40 * 640 /750,上边距是50 * 1138 / 1334。

至此,View的位置、大小都已经处理完毕。然后我们还需要处理字体、内边距、圆角等。

适配字体

UIButton、UITextView、UILabel、UITextField等需要适配字体,这些和宽高一样属于View的自身属性,与其他的View没有关系。所以可以和处理宽高写在一起。

    if([view isKindOfClass:[UIButton class]]){
        UIButton *button=(UIButton *)view;
        [self autoUIButton:button:wRate:hRate];
    }else if([view isKindOfClass:[UILabel class]]){
        UILabel *label = (UILabel *)view;
        [self autoUILabel:label:wRate :hRate];
    }else if([view isKindOfClass:[UITextField class]]){
        UITextField *textField = (UITextField *)view;
        [self autoUITextField:textField :wRate :hRate];
    }else if([view isKindOfClass:[UITextView class]]){
        UITextView *textView = (UITextView *)view;
        [self autoTextView:textView :wRate :hRate];
    }else if([view isKindOfClass:[UIImageView class]]){
        UIImageView *imageView = (UIImageView *)view;
        [self autoUIImageView:imageView :wRate :hRate];
    }

对每一种View需要单独处理:比如UIButton、UITextView等需要处理字体大小。
UIButton适配字体:
+(void)autoUIButton:(UIButton *)button:(float)wRate:(float)hRate{
//适配字体
textRate = wRate;
float textSize =button.titleLabel.font.pointSize * textRate;
button.titleLabel.font = [UIFont systemFontOfSize: textSize];
}
UITextView字体适配:
+(void)autoTextView:(UITextView *)textView :(float)wRate :(float)hRate{
//适配字体
textRate = hRate;
float textSize =textView.font.pointSize * textRate;
textView.font =[UIFont systemFontOfSize: textSize];
}
这里字体适配可以根据自己的需要来选择字体是根据宽度、高度或者对角线的缩放比例来缩放,按照最终效果适当调整。

图片处理

iOS在设置图片宽高时可以直接指定宽高的比例,这一点很赞。不用担心原本宽高一样的图片缩放后变形。

状态栏处理

由于iphoneX的状态栏高度和之前的手机状态栏不一样,所以适配时需要考虑到状态栏高度。可以把原本的设计图尺寸高度减去状态栏的高度当作标准,比如原本750*1334的设计图,减去状态栏高度后就是750 * 1294,当然一直对应获取屏幕高度是也要减去状态栏高度,这样缩放比例才是正常的。

导航栏处理

和状态栏处理类似,减去系统导航栏的高度再进行缩放。没有测试过每一个手机的导航栏高度是多少,之后会测试一下每一个手机。如果有tabbar的话,处理也是类似。

后续改进

至此我们已经可以完成基本布局的适配, 后续还需要适配UIButton等的背景图片,距离水平中心20等特殊属性,性能方面还需要测试。

有些东西没说明白的,可以去通过上面的连接去查看鸿洋大神的介绍,虽然是Android,不过道理是一样的。

代码很乱,之后会改进,轻喷。

//
// AutoUtils.h
// ScreenPercent
//
// Created by D on 2018/2/1.
// Copyright © 2018年 D. All rights reserved.
//

#import 
#import 
#import 

@interface AutoUtils : NSObject

@property float designWidth;//设计宽
@property float designHeight;//设计高
@property float _wRate;//宽拉伸比例
@property float _hRate;//高拉伸比例


/**
 初始化
 todo  加入对tabbar的处理
 @param designWidth 设计图尺寸的宽度
 @param designHeight 设计图尺寸的高度(不包含状态栏)
 */
+(void)initAuto:(float) designWidth:(float) designHeight;


/**
 适配UIViewController
 这个方法主要是为了之后处理UINavigationBar、UITabBar
 */
+(void)autoUIViewController:(UIViewController *)viewController;

/**

 */

/**
 适配UIViewController
 这个方法主要是为了之后处理UINavigationBar、UITabBar

 @param viewController UIViewController对象
 @param hasNavigationBar 是否有NavigationBar
 @param hasTabBar 是否有TabBar
 */
+(void)autoUIViewController:(UIViewController *)viewController
                           :(Boolean)hasNavigationBar
:(Boolean)hasTabBar;


/**
 获取navigationbar高度

 @return float
 */
+(float)getNavigationBarHeight:(UIViewController *)viewController;

/**
 获取tabbar高度

 @return float
 */
+(float)getTabBarHeight:(UIViewController *)viewController;

/**
 适配普通的View

 @param view void
 */
+(void)autoView:(UIView *)view;

/**
 缩放后的宽度

 @param viewWidth 要缩放View的宽度
 @param designWidth 设计图的宽度尺寸  例如375
 @return <#return value description#>
 */
+(float)getDisplayWidth:(float) viewWidth:(float) designWidth;

/**
 Description 缩放后的高度

 @param viewHeight 要缩放的View的高度
 @param designHeight 设计图的高度尺寸,去掉状态之后的高度  例如647
 @return <#return value description#>
 */
+(float)getDisplayHeight:(float) viewHeight:(float) designHeight;
/**
 含有子View的View,需要处理子View之间以及子View与父View的距离
 */
+(void)autoRelatives:(UIView *)view:(float)wRate:(float)hRate;

/**
 不含有子View的View 处理自身的宽高信息

 @param view <#view description#>
 */
+(void)autoSelf:(UIView *)view:(float)wRate:(float)hRate;

/**
 适配button

 @param button 要适配的button
 @param wRate 水平方向拉伸比例
 @param hRate 竖直方向拉伸比例
 */
+(void)autoUIButton:(UIButton *)button:(float)wRate:(float)hRate;

/**

 适配textView
 @param textView 要适配的textView
 @param wRate 水平方向拉伸比例
 @param hRate 竖直方向拉伸比例
 */
+(void)autoTextView:(UITextView *)textView:(float)wRate:(float)hRate;

/**
 适配label

 @param label 要适配的label
 @param wRate 水平方向拉伸比例
 @param hRate 竖直方向拉伸比例
 */
+(void)autoUILabel:(UILabel *)label:(float)wRate:(float)hRate;

/**
 适配textField

 @param textField 要适配的textField
 @param wRate 水平方向拉伸比例
 @param hRate 竖直方向拉伸比例
 */
+(void)autoUITextField:(UITextField *)textField:(float)wRate:(float)hRate;

/**
 适配imageView
 如果宽高都按照宽缩放,那么固定宽度(如50),高度通过设置aspect ratio
 同理宽高都按照高缩放,那么固定高度(如50),宽度通过设置aspect ratio
 如果宽按宽缩放,高按照高缩放,那么设置宽高的值即可

 @param textField 要适配的imageView
 @param wRate 水平方向拉伸比例
 @param hRate 竖直方向拉伸比例
 */
+(void)autoUIImageView:(UIImageView *)imageView:(float)wRate:(float)hRate;
@end


//
//  AutoUtils.m
//  ScreenPercent
//
//  Created by D on 2018/2/1.
//  Copyright © 2018年 D. All rights reserved.
//


#import "AutoUtils.h"


//默认的设计图尺寸
static float designWidth = 375.0;
static float designHeight = 667.0;
//屏幕显示区域的宽高
static float displayWidth = 0;
static float displayHeight = 0;


//只去除状态栏的高度后的缩放比例
static float designWidthRate = 0;
static float designHeightRate = 0;

static float textRate = 0;


//暂时不考虑navigationbar和tabbar等的影响,目前只适配基本控件

@implementation AutoUtils

+(void)initAuto:(float)designW:(float)designH{

    //设计图尺寸
    designWidth = designW;
    designHeight = designH;

    //导航栏高度
    displayWidth =[UIScreen mainScreen].bounds.size.width;
    displayHeight = [UIScreen mainScreen].bounds.size.height;

    //计算缩放比例
    designWidthRate = displayWidth / designWidth;
    designHeightRate= displayHeight / designHeight;
    textRate =displayWidth /designWidth;
//    textRate =hypotf(displayWidth,displayHeight) /hypotf(designWidth,designHeight);
}
+(void)autoUIViewController:(UIViewController *)viewController{
    [self autoUIViewController:viewController :false :false];
}

+(void)autoUIViewController:(UIViewController *)viewController :(Boolean)hasNavigationBar :(Boolean)hasTabBar{
    float navigationBarHeight = 0;
    float tabBarheight = 0;
    if(hasNavigationBar){
        navigationBarHeight = [self getNavigationBarHeight:viewController];
    }
    if(hasTabBar){
        tabBarheight = [self getTabBarHeight:viewController];
    }
    [self autoSelf:viewController.view:designWidthRate:designHeightRate];
    [self autoRelatives:viewController.view:designWidthRate:designHeightRate];
//
}
+(void)autoView:(UIView *)view{
    [self autoSelf:view:designWidthRate:designHeightRate];//处理自己的宽高
    //有子View
    [self autoRelatives:view:designWidthRate:designHeightRate];//处理
}

+(void)autoRelatives:(UIView *)view :(float)wRate :(float)hRate{

    //必须有子View
    NSArray *subviews = [view subviews];
      if ([subviews count] == 0){
          return;
      }
    //适配子View之间的间距,适配子View与自己的间距
    //目前只处理上下左右的间距
    NSArray* constrains = view.constraints;
//    printf("最外层的contrains count--%lu\n",constrains.count);
//    printf("NSLayoutAttributeLeft%lu\n",NSLayoutAttributeLeft);
//printf("NSLayoutAttributeRight%lu\n",NSLayoutAttributeRight);    printf("NSLayoutAttributeTop%lu\n",NSLayoutAttributeTop);    printf("NSLayoutAttributeBottom%lu\n",NSLayoutAttributeBottom);    printf("NSLayoutAttributeLeading%lu\n",NSLayoutAttributeLeading);    printf("NSLayoutAttributeTrailing%lu\n",NSLayoutAttributeTrailing);    printf("NSLayoutAttributeWidth%lu\n",NSLayoutAttributeWidth);    printf("NSLayoutAttributeHeight%lu\n",NSLayoutAttributeHeight);    printf("NSLayoutAttributeCenterX%lu\n",NSLayoutAttributeCenterX);
//printf("NSLayoutAttributeCenterY%lu\n",NSLayoutAttributeCenterY);
//    printf("NSLayoutAttributeLastBaseline%lu\n",NSLayoutAttributeLastBaseline);
//
//  printf("stop\n");
    for (NSLayoutConstraint* constraint in constrains) {
//        printf("%lu--%lu--%f\n",constraint.firstAttribute,constraint.secondAttribute,constraint.constant);
        CGFloat temp=constraint.constant;
        if(temp!=0){
            //这里只需要判断firstAttribute即可
            //因为leading只可能跟leading或者trailing有关系,都是水平的
            if (constraint.firstAttribute == NSLayoutAttributeLeading
                || constraint.firstAttribute == NSLayoutAttributeTrailing) {
                printf("find width\n");
                constraint.constant = temp * wRate;//左右水平缩放
            }else if(constraint.firstAttribute ==NSLayoutAttributeTop
                     ||constraint.firstAttribute == NSLayoutAttributeBottom){
                       printf("find heihgt\n");
                constraint.constant  = temp * hRate;//上下竖直缩放
            }
        }
    }

    //接着循环遍历处理每一个子View
    for (UIView *subview in subviews) {
        [self autoView:subview];
    }
}

+(void)autoSelf:(UIView *)view:(float)wRate:(float)hRate{


    printf("------View自己的属性------\n");
    printf("NSLayoutAttributeLeft%lu\n",NSLayoutAttributeLeft);
    printf("NSLayoutAttributeRight%lu\n",NSLayoutAttributeRight);    printf("NSLayoutAttributeTop%lu\n",NSLayoutAttributeTop);    printf("NSLayoutAttributeBottom%lu\n",NSLayoutAttributeBottom);    printf("NSLayoutAttributeLeading%lu\n",NSLayoutAttributeLeading);    printf("NSLayoutAttributeTrailing%lu\n",NSLayoutAttributeTrailing);    printf("NSLayoutAttributeWidth%lu\n",NSLayoutAttributeWidth);    printf("NSLayoutAttributeHeight%lu\n",NSLayoutAttributeHeight);    printf("NSLayoutAttributeCenterX%lu\n",NSLayoutAttributeCenterX);
    printf("NSLayoutAttributeCenterY%lu\n",NSLayoutAttributeCenterY);
        printf("NSLayoutAttributeLastBaseline%lu\n",NSLayoutAttributeLastBaseline);

    //所有的view都需要适配宽高
    NSArray* constrains = view.constraints;
      printf("最外层的contrains count--%lu\n",constrains.count);
    for (NSLayoutConstraint* constraint in constrains) {
        CGFloat temp =constraint.constant;
        printf("constant is %ld =%f\n",constraint.firstAttribute,temp);
        if(temp!=0){
            if (constraint.firstAttribute == NSLayoutAttributeHeight) {
                printf("适配前高度的约束--%f\n",constraint.constant);
                constraint.constant  = temp * hRate;
                printf("适配后高度的约束--%f\n",constraint.constant);
            }else if(constraint.firstAttribute == NSLayoutAttributeWidth){
                printf("适配前宽度的约束--%f\n",constraint.constant);
                constraint.constant  = temp * wRate;
                printf("适配后宽度的约束--%f\n",constraint.constant);
            }
        }
    }

    //是否button
    if([view isKindOfClass:[UIButton class]]){
        UIButton *button=(UIButton *)view;
        [self autoUIButton:button:wRate:hRate];
              printf("------以上时UIButton自己的属性------\n");
    }else if([view isKindOfClass:[UILabel class]]){
        UILabel *label = (UILabel *)view;
        [self autoUILabel:label:wRate :hRate];
                      printf("-----以上时UILabel自己的属性------\n");
    }else if([view isKindOfClass:[UITextField class]]){
        UITextField *textField = (UITextField *)view;
        [self autoUITextField:textField :wRate :hRate];
              printf("------以上时UITextField自己的属性------\n");
    }else if([view isKindOfClass:[UITextView class]]){
        UITextView *textView = (UITextView *)view;
        [self autoTextView:textView :wRate :hRate];
            printf("------以上是UITextView自己的属性------\n");
    }else if([view isKindOfClass:[UIImageView class]]){
        UIImageView *imageView = (UIImageView *)view;
        [self autoUIImageView:imageView :wRate :hRate];
        printf("------以上是UIImageView自己的属性------\n");
    }
    }

    +(float)getDisplayWidth:(float)viewWidth :(float)designWidth{
        return viewWidth *  displayWidth /  designWidth;
    }
    +(float)getDisplayHeight:(float)viewHeight :(float)designHeight{
        return viewHeight * displayHeight / designHeight;
    }

    /**
     目前不考虑Navigationbar的影响
     之后减去navigationbar高度
     或者对navigationbar的高度进行适配

     @param viewController viewController
     @return 0
     */
    +(float)getNavigationBarHeight:(UIViewController *)viewController{
        return 0;
    // return viewController.navigationController.navigationBar.frame.size.height;
    }

    /**
     目前不考虑tabbar的影响
     之后减去tabbar高度
     或者对tabbar的高度进行适配

     @param viewController viewController
     @return 0
     */
    +(float)getTabBarHeight:(UIViewController *)viewController{
        return 0;
    //    return viewController.tabBarController.tabBar.frame.size.height;
    }

    +(void)autoUIButton:(UIButton *)button:(float)wRate:(float)hRate{
        textRate = wRate;
        printf("适配button\n");
        printf("button child view 个数 %ld\n", button.subviews.count);

        //获取约束
        NSArray* constrains = button.constraints;

        printf("contrains count--%lu\n",constrains.count);

        //适配字体
        float textSize =button.titleLabel.font.pointSize * textRate;
        button.titleLabel.font = [UIFont systemFontOfSize: textSize];

    }

    +(void)autoUILabel:(UILabel *)label :(float)wRate :(float)hRate{
       textRate = wRate;
        printf("适配label\n");
        printf("label child view 个数 %ld\n", label.subviews.count);
        //适配字体
        float textSize =label.font.pointSize * textRate;
        label.font = [UIFont systemFontOfSize: textSize];

    }
    +(void)autoTextView:(UITextView *)textView :(float)wRate :(float)hRate{
        textRate = wRate;
        printf("适配textView\n");
        printf("textView child view 个数 %ld\n", textView.subviews.count);
        //适配字体
        float textSize =textView.font.pointSize * textRate;
        textView.font =[UIFont systemFontOfSize: textSize];
    }
    +(void)autoUITextField:(UITextField *)textField :(float)wRate :(float)hRate{
        textRate = wRate;
        printf("适配textField\n");
        printf("textField child view 个数 %ld\n", textField.subviews.count);
        //适配字体
        float textSize =textField.font.pointSize * textRate;
        textField.font =[UIFont systemFontOfSize: textSize];

    }

    +(void)autoUIImageView:(UIImageView *)imageView :(float)wRate :(float)hRate{

    }
    @end

调用
- (void)viewDidLoad {
[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.


[AutoUtils initAuto:375.0:647.0];
[AutoUtils autoUIViewController:self :false :false]; 

}

tableView适配
在contentView套一层UIView,指定contentView与UIView高度一致,通过指定UIView的高度来设置contentView的高度。

获取cell的时候对cell进行适配即可。
[AutoUtils autoView:cell];

你可能感兴趣的:(iOS)