1. 程式人生 > >iOS xib檔案根據螢幕等比例縮放的適配

iOS xib檔案根據螢幕等比例縮放的適配

前言

   在此我不是和大家討論,xib相對約束的使用,因為這些文章網上有一大堆的資料,這也不是我今天想要講的東西。
   不知道大家平常有沒有碰到過這樣的情況。相信很多人在開發中都會使用storyboard和xib來寫介面,所見即所得,拖拖拽拽就大工告成了,爽的很。不像純程式碼寫介面,還要各種alloc、addSubview。可提測後,UI設計師坐在你身邊,對UI各種細節調整席捲而來。因為一般設計師是按照4.7螢幕來設計的,這個螢幕下的顯示效果沒問題,到了小點的螢幕或者大點螢幕的時候,就各種不滿意了,各個控制元件的比例關係並不是他們想要的。每當這個時候就要從xib拉出NSLayoutConstraint屬性來動態設定了。還有字型也是,xib設定了,還要鑽進程式碼裡面再動態設定一番,真是噁心死了!
    所以我在想為什麼不能像安卓螢幕適配一樣,一切都是等比例適配,我們只要對著一個螢幕尺寸去做開發,其他的自動等比例縮放(按照基準螢幕的寬度去縮放,比如螢幕寬度375的控制元件width = 10pt,那麼螢幕寬度750時就是width = 20pt了)。

解決方案

    下文就是我針對上面的問題,提出的三種解決方案。

第一種:純程式碼實現

    為了能夠更加好的控制這些UI控制元件的佈局和設定,我開始在新專案中用純程式碼去寫介面了。雖然用的是Masonnry自動佈局,但也難免要設定具體的值,在設值時,我會在加一層AdaptW(floatValue)巨集定義包裝。

- (void)private_addConstraintForSubViews
{ 
    [self.titleView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.height.mas_equalTo(AdaptW(55));
        make.left.right.equalTo(self);
        make.top.equalTo(self);
    }];
    
    [self.pageCtl mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.bottom.right.equalTo(self);
        make.height.mas_equalTo(AdaptW(8));
    }];
}

   AdaptW(floatValue)其實就是一個BSFitdpiUtil工具類方法的呼叫,以常用的基準螢幕,iphone 6 的375x667尺寸去換算的。程式碼如下:

#define kRefereWidth 375.0 // 參考寬度
#define kRefereHeight 667.0 // 參考高度

#define AdaptW(floatValue) [BSFitdpiUtil adaptWidthWithValue:floatValue]

#import <Foundation/Foundation.h>

@interface BSFitdpiUtil : NSObject


/**
 以螢幕寬度為固定比例關係,來計算對應的值。假設:參考螢幕寬度375,floatV=10;當前螢幕寬度為750時,那麼返回的值為20
 @param floatV 參考螢幕下的寬度值
 @return 當前螢幕對應的寬度值
 */
+ (CGFloat)adaptWidthWithValue:(CGFloat)floatV;

@end
#import "BSFitdpiUtil.h"

@implementation BSFitdpiUtil

+ (CGFloat)adaptWidthWithValue:(CGFloat)floatV;
{
    return floatV*[[UIScreen mainScreen] bounds].size.width/kRefereWidth;
}
@end

    字型大小的設定,我也是用這種工具類的換算的包裝來實現的。

    self.bottomLab = [UILabel new];
    [self addSubview:self.bottomLab];
    self.bottomLab.font = kDefaultFont(Adapt(15));
    self.bottomLab.textColor = kFirstTextColor;
    self.bottomLab.textAlignment = NSTextAlignmentCenter;

    從此我再也不怕UI設計師來對UI細節了,你要等比例我就等比例給你看,不需要我就在BSFitdpiUtil工具類的adaptWidthWithValue方法,return一個原始值floatV。

第二種:利用IBInspectable關鍵字和分類

   後來我到了新公司接手了箇舊專案,工程裡幾乎所有的介面都是用xib來寫的。慘了,UI設計師同事還跟我說,新寫的介面都要等比例縮放,不然就要各種大小不一的螢幕對一下,我累她也累。
   就是因為這種適配的問題,我兩年前開始放棄了視覺化的佈局介面方式,改用純程式碼。這次我想保持專案風格的統一,而且也想再次擁抱storyboard和xib,通過查詢資料找到利用IBInspectable關鍵字和分類來實現等比例縮放的功能 ( IBInspectable 就是能夠讓你的自定義 UIView 的屬性出現在 IB 中 Attributes inspector)。具體做法就是:

1.寫一個NSLayoutConstraint的分類
2.新增adapterScreen的屬性(Bool 值,yes代表需要對螢幕進行等比例適配)

 #import <UIKit/UIKit.h>

@interface NSLayoutConstraint (BSIBDesignable)

@property(nonatomic, assign) IBInspectable BOOL adapterScreen;

@end

3.在adapterScreen的set方法裡面對NSLayoutConstraint物件的constant值進行換算

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

// 基準螢幕寬度
#define kRefereWidth 375.0
// 以螢幕寬度為固定比例關係,來計算對應的值。假設:基準螢幕寬度375,floatV=10;當前螢幕寬度為750時,那麼返回的值為20
#define AdaptW(floatValue) (floatValue*[[UIScreen mainScreen] bounds].size.width/kRefereWidth)


@implementation NSLayoutConstraint (BSIBDesignable)

//定義常量 必須是C語言字串
static char *AdapterScreenKey = "AdapterScreenKey";

- (BOOL)adapterScreen{
    NSNumber *number = objc_getAssociatedObject(self, AdapterScreenKey);
    return number.boolValue;
}

- (void)setAdapterScreen:(BOOL)adapterScreen {
    
    NSNumber *number = @(adapterScreen);
    objc_setAssociatedObject(self, AdapterScreenKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    if (adapterScreen){
        self.constant = AdaptW(self.constant);
    }
}

@end

4.將該分類匯入到工程中,就可以看到xib所有的約束有adapterScreen的屬性了,切換至on,就可以達到想要的等比例適配效果了。

 

xib等比例.png

    除了給NSLayoutConstraint新增adapterScreen屬性,也可以用同樣的方式給UILabel、UIButton等對字型大小等比例縮放。但有個很大的缺點就是一個介面有很多控制元件,每個控制元件都有Constraints,這個集合裡面每個約束都要設定adapterScreen的開關,太麻煩了,而且一旦要對舊的介面也行進同樣的操作,想死的心都有。為了解決這個問題,想了個第三種方法。

第三種:用分類去遍歷一個view上需要操作的目標並換算

   這個方法其實原理很簡單,核心就是一個個遍歷換算。程式碼如下

#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, BSAdaptScreenWidthType) {
    AdaptScreenWidthTypeNone = 0, 
    BSAdaptScreenWidthTypeConstraint = 1<<0, /**< 對約束的constant等比例 */
    BSAdaptScreenWidthTypeFontSize = 1<<1, /**< 對字型等比例 */
    BSAdaptScreenWidthTypeCornerRadius = 1<<2, /**< 對圓角等比例 */
    BSAdaptScreenWidthTypeAll = 1<<3, /**< 對現有支援的屬性等比例 */
};

@interface UIView (BSAdaptScreen)

/**
 遍歷當前view物件的subviews和constraints,對目標進行等比例換算
 
 @param type 想要和基準螢幕等比例換算的屬性型別
 @param exceptViews 需要對哪些類進行例外
 */
- (void)adaptScreenWidthWithType:(BSAdaptScreenWidthType)type
                     exceptViews:(NSArray<Class> *)exceptViews;

@end
#import "UIView+BSAdaptScreen.h"

// 基準螢幕寬度
#define kRefereWidth 375.0
// 以螢幕寬度為固定比例關係,來計算對應的值。假設:基準螢幕寬度375,floatV=10;當前螢幕寬度為750時,那麼返回的值為20
#define AdaptW(floatValue) (floatValue*[[UIScreen mainScreen] bounds].size.width/kRefereWidth)

@implementation UIView (BSAdaptScreen)

- (void)adaptScreenWidthWithType:(BSAdaptScreenWidthType)type
                      exceptViews:(NSArray<Class> *)exceptViews {
    if (type == AdaptScreenWidthTypeNone)  return;
    if (![self isExceptViewClassWithClassArray:exceptViews]) {
     
        // 是否要對約束進行等比例
        BOOL adaptConstraint = ((type & BSAdaptScreenWidthTypeConstraint) || type == BSAdaptScreenWidthTypeAll);
        
        // 是否對字型大小進行等比例
        BOOL adaptFontSize = ((type & BSAdaptScreenWidthTypeFontSize) || type == BSAdaptScreenWidthTypeAll);
        
        // 是否對圓角大小進行等比例
        BOOL adaptCornerRadius = ((type & BSAdaptScreenWidthTypeCornerRadius) || type == BSAdaptScreenWidthTypeAll);
        
        // 約束
        if (adaptConstraint) {
            [self.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull subConstraint, NSUInteger idx, BOOL * _Nonnull stop) {
                subConstraint.constant = AdaptW(subConstraint.constant);
            }];
        }
        
        // 字型大小
        if (adaptFontSize) {
            
            if ([self isKindOfClass:[UILabel class]] && ![self isKindOfClass:NSClassFromString(@"UIButtonLabel")]) {
                UILabel *labelSelf = (UILabel *)self;
                labelSelf.font = [UIFont systemFontOfSize:AdaptW(labelSelf.font.pointSize)];
            }
            else if ([self isKindOfClass:[UITextField class]]) {
                UITextField *textFieldSelf = (UITextField *)self;
                textFieldSelf.font = [UIFont systemFontOfSize:AdaptW(textFieldSelf.font.pointSize)];
            }
            else  if ([self isKindOfClass:[UIButton class]]) {
                UIButton *buttonSelf = (UIButton *)self;
                buttonSelf.titleLabel.font = [UIFont systemFontOfSize:AdaptW(buttonSelf.titleLabel.font.pointSize)];
            }
            else  if ([self isKindOfClass:[UITextView class]]) {
                UITextView *textViewSelf = (UITextView *)self;
                textViewSelf.font = [UIFont systemFontOfSize:AdaptW(textViewSelf.font.pointSize)];
            }
        }
        
        // 圓角
        if (adaptCornerRadius) {
            if (self.layer.cornerRadius) {
                self.layer.cornerRadius = AdaptW(self.layer.cornerRadius);
            }
        }
        
        [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subView, NSUInteger idx, BOOL * _Nonnull stop) {
            // 繼續對子view操作
            [subView adaptScreenWidthWithType:type exceptViews:exceptViews];
        }];
    }
}

// 當前view物件是否是例外的檢視
- (BOOL)isExceptViewClassWithClassArray:(NSArray<Class> *)classArray {
    __block BOOL isExcept = NO;
    [classArray enumerateObjectsUsingBlock:^(Class  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([self isKindOfClass:obj]) {
            isExcept = YES;
            *stop = YES;
        }
    }];
    return isExcept;
}
@end

最後,不管是用xib拖控制元件拉約束,還是用純程式碼的形式寫介面,只要在程式碼裡對父檢視調個方法就可以對其本身和子檢視,進行約束和字型大小等比例換算了。例如對某個viewcontroller上所有的view進行等比例換算的佈局

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self setup];
    [self.view adaptScreenWidthWithType:BSAdaptScreenWidthTypeAll exceptViews:nil];
}

另外我寫了個小小的demo: https://github.com/LvBisheng/TestScaleWithScreen。 我只是提供了一個思路,大家可以根據需要自行對分類進行更改。

總結

   這個問題其實之前困擾我蠻久的,每次想解決,可搜了下網上相關的資料和討論很少。有時覺得是不是這個等比例換算的需求,本身就需要再斟酌斟酌?還是說大家都有更好更方便的解決方案了......



作者:小生不怕
連結:https://www.jianshu.com/p/cf049bebdc6c
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。