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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。