概述
關(guān)于純代碼和 xib / StoryBoard 構(gòu)建UI 的爭論在 iOS 開發(fā)中似乎從未停止過。本篇文章我們不討論二者孰優(yōu)孰劣,只簡單的為 xib / StoryBoard 擴(kuò)展一些實(shí)用屬性,讓我們的 IB 更加順手。
xib / Storyboard 的局限性
使用 xib / Storyboard 這類可視化工具開發(fā),最大的優(yōu)點(diǎn)來自于構(gòu)建 UI 方便、快捷、直觀。但是同樣的開發(fā)時(shí)也會(huì)有很多的局限性,這里我們先不談繼承和復(fù)用,試想有這樣兩個(gè)場景。
- 需要在不同尺寸的屏幕上顯示不同大小的字體。這點(diǎn)用純代碼很容易實(shí)現(xiàn),或者是把 IB 中的視圖拖線出去再用代碼設(shè)置也能實(shí)現(xiàn)。
那么有沒有方法可以直接就在 IB 中設(shè)置,自動(dòng)按屏幕比例去放大和縮小字體呢?
- 有一個(gè)控件,假如為它設(shè)置了一個(gè)相對于左邊30,頂部60,寬高100的約束。能不能讓他在不同尺寸的屏幕上,按屏幕比例去自適應(yīng)控件的大小和間距呢。
同樣的用純代碼布局或者把控件的約束拖線出去用代碼設(shè)置也能實(shí)現(xiàn),那么有沒有方法可以直接就在 IB 中設(shè)置呢?
其實(shí)要想實(shí)現(xiàn)以上效果都非常簡單,本篇將提供 OC + Swift 雙版本的代碼。
讓 IB 擁有自適應(yīng)的屬性
日常開發(fā)中, UI 通常只會(huì)為我們標(biāo)注一種屏幕尺寸的圖,具體對于不同屏幕大小的適配得自己去處理。
比如剛剛的第二個(gè)例子,在5.5寸屏幕下,UI 告訴你一個(gè) 控件,相對于左邊30,頂部60,寬高100。當(dāng)然你也可以直接使用這個(gè)約束讓所有尺寸設(shè)備都顯示這個(gè)約束,但大多數(shù)時(shí)候,這種固定約束的顯示效果都是不盡人意的。
這時(shí)候我們就需要按比例去動(dòng)態(tài)計(jì)算實(shí)際的約束大小了,由于水平向和豎直方向的縮放比例是不同的,我們把約束計(jì)算區(qū)分成橫向和縱向,算法就是非常簡單的比例轉(zhuǎn)換。
- 橫向:當(dāng)前的屏幕寬度 / UI 給你標(biāo)注圖的設(shè)備寬度(比如說5.5寸設(shè)備的414) * 你需要縮放的值(比如說30)
- 縱向:當(dāng)前的屏幕高度 / UI 給你標(biāo)注圖的設(shè)備高度(比如說5.5寸設(shè)備的736) * 你需要縮放的值(比如說60)
轉(zhuǎn)換成代碼,實(shí)際的屏幕尺寸按 UI 給你的圖來修改,這里用5.5寸的。
- OC
#define KScaleH(c) [[UIScreen mainScreen] bounds].size.width / 414 * c
#define KScaleV(c) [[UIScreen mainScreen] bounds].size.width / 736 * c
- Swift
private func KScaleH(_ c: CGFloat) -> CGFloat {
return UIScreen.main.bounds.width / 414 * c
}
private func KScaleV(_ c: CGFloat) -> CGFloat {
return UIScreen.main.bounds.width / 736 * c
}
以上把大概原理說完,我們就要寫具體的實(shí)現(xiàn)了。首先,為了能在 xib / StoryBoard 中直接設(shè)置屬性,我們需要了解兩個(gè)關(guān)鍵詞@IBDesignable 和 @IBInspectable,這倆是 iOS8 的新特性,可以實(shí)時(shí)渲染在interface builder上,并且直接修改就能發(fā)生變化。這里我們主要使用 @IBInspectable
我們創(chuàng)建一個(gè) NSLayout 的 Category / Extension,添加 @IBInspectable 屬性
- OC(Category)
/** 水平方向約束*/
@property (nonatomic, assign)IBInspectable CGFloat horizontalConstant;
/** 豎直方向約束 */
@property (nonatomic, assign)IBInspectable CGFloat verticalConstant;
- Swift(Extension)
/// 水平方向約束
@IBInspectable var horizontalConstant: CGFloat
/// 豎直方向約束
@IBInspectable var verticalConstant: CGFloat
添加完成之后在 xib / StoryBoard 中就能看到效果了,所有拖出來的約束都擁有了這兩個(gè)屬性
以上都是簡單的聲明屬性,還沒有具體的實(shí)現(xiàn),現(xiàn)在我們用 Runtime 為屬性添加實(shí)現(xiàn)。
- OC
static void *horizontal_key = &horizontal_key;
static void *vertical_key = &horizontal_key;
- (void)setHorizontalConstant:(CGFloat)horizontalConstant {
self.constant = KScaleH(horizontalConstant);
objc_setAssociatedObject(self, &horizontal_key, @(horizontalConstant), OBJC_ASSOCIATION_ASSIGN);
}
- (CGFloat)horizontalConstant {
return [objc_getAssociatedObject(self, &horizontal_key) floatValue];
}
- (void)setVerticalConstant:(CGFloat)verticalConstant {
self.constant = KScaleV(verticalConstant);
objc_setAssociatedObject(self, &vertical_key, @(verticalConstant), OBJC_ASSOCIATION_ASSIGN);
}
- (CGFloat)verticalConstant {
return [objc_getAssociatedObject(self, &vertical_key) floatValue];
}
- Swift
private var horizontal_key = 100
private var vertical_key = 200
/// 水平方向約束
@IBInspectable var verticalConstant: CGFloat {
set {
constant = KScaleV(verticalConstant)
objc_setAssociatedObject(self, &vertical_key, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
get {
if let vc = objc_getAssociatedObject(self, &vertical_key) as? CGFloat {
return vc
}
return constant
}
}
/// 豎直方向約束
@IBInspectable var horizontalConstant: CGFloat {
set {
constant = KScaleH(horizontalConstant)
objc_setAssociatedObject(self, &horizontal_key, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
get {
if let hc = objc_getAssociatedObject(self, &horizontal_key) as? CGFloat {
return hc
}
return constant
}
}
好了,現(xiàn)在我們的自適應(yīng)約束就完成了,接下來再設(shè)置字體大小自適應(yīng),相信看到這里你心里也有了個(gè)大概,只要把上面的約束值換成字體大小即可,字體通常我們只需要用水平方向的比例去縮放。
這里需要注意的是,由于蘋果的限制,我們是不能直接為 UIFont 對象添加 @IBInspectable 的,所以我們只能添加字體大小的屬性,具體的字體類型再從實(shí)際的設(shè)置中取到,這點(diǎn)還是比較遺憾的。
這里我們只為 UILabel 的字體大小添加了擴(kuò)展,其他的 UIButton、UITextView 等等都是一樣的思路,代碼我就不貼了,有不懂的可以留言。
- OC
static void *fitFontSize_key = &fitFontSize_key;
- (void)setFitFontSize:(CGFloat)fitFontSize {
self.font = [UIFont fontWithName:self.font.fontName size:KScaleH(fitFontSize)];
objc_setAssociatedObject(self, &fitFontSize_key, @(fitFontSize), OBJC_ASSOCIATION_ASSIGN);
}
- (CGFloat)fitFontSize {
return [objc_getAssociatedObject(self, &fitFontSize_key) floatValue];
}
- Swift
@IBInspectable var fitFontSize: CGFloat {
set {
font = UIFont(name: font.fontName, size: KScaleH(fitFontSize))
objc_setAssociatedObject(self, &fitFontSizeKey, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
get {
if let fs = objc_getAssociatedObject(self, &fitFontSizeKey) as? CGFloat {
return fs
}
return font.pointSize
}
}
最后看一下運(yùn)行效果,Demo 地址 Fit_Demo
