iOS開(kāi)發(fā):屏幕適配淺談

前端開(kāi)發(fā)的屏幕適配其實(shí)算是基本功垢袱,每個(gè)碼農(nóng)在長(zhǎng)期實(shí)踐中都有自己的總結(jié)盹沈。

在iOS平臺(tái)上敦捧,蘋(píng)果爸爸對(duì)適配的支持個(gè)人感覺(jué)很不人性化扫责,提供了autoLayout蝙叛、sizeClass等技術(shù),感覺(jué)沒(méi)有前端類(lèi)似flexBox這樣的技術(shù)來(lái)得靈活公给。像是點(diǎn)歪了技能樹(shù),過(guò)于重視使用xib配置UI蜘渣,但很多碼農(nóng)還是習(xí)慣純代碼編程淌铐。Cocoa沒(méi)有css這樣的純布局文件,導(dǎo)致很多時(shí)候我們將布局蔫缸、UI腿准、邏輯寫(xiě)在一起,十分混亂拾碌,冗長(zhǎng)吐葱。

下面簡(jiǎn)單介紹下在實(shí)踐中適配屏幕的方向思路,拋磚引玉校翔。

從設(shè)計(jì)到代碼:溝通與標(biāo)準(zhǔn)

App的UI界面是由設(shè)計(jì)人員(產(chǎn)品弟跑,UI)繪制的,然后由開(kāi)發(fā)實(shí)現(xiàn)防症,雙方要有良好的溝通孟辑,并且把設(shè)計(jì)內(nèi)容標(biāo)準(zhǔn)化,文檔化蔫敲。

對(duì)設(shè)計(jì)方來(lái)說(shuō)饲嗽,適配的規(guī)則總是在設(shè)計(jì)師心中的,是按比例的縮放奈嘿,還是固定的間距貌虾,是公用一套規(guī)則,還是在大屏下有特殊的布局裙犹,都需要有明確方式傳達(dá)給耿直的碼農(nóng)們尽狠。

良好的設(shè)計(jì)文檔是溝通第一步

一般常見(jiàn)的布局方式有:

  • 固定間距:在不同尺寸下衔憨,間距總是固定
  • 流式布局:文字,圖片等在不同屏幕下流式排布晚唇,比如大屏下一行顯示四張圖片巫财,小屏一行三張,圖片尺寸固定
  • 比例放大:間距哩陕,文字大小平项,圖片大小等比例放大
  • 保持比值:兩個(gè)UI元素或者圖片的長(zhǎng)寬等屬性保持一定的比值
  • 對(duì)齊:元素間按某個(gè)方向?qū)R

設(shè)計(jì)師需要將這些布局規(guī)則標(biāo)注清楚,有利溝通悍及,也方便文檔化日后追溯闽瓢。

對(duì)于一些通用UI組件,要進(jìn)行標(biāo)準(zhǔn)化心赶,設(shè)計(jì)上有利于app風(fēng)格統(tǒng)一扣讼,實(shí)現(xiàn)上也方便開(kāi)發(fā)進(jìn)行必要的封裝。

平面設(shè)計(jì)要標(biāo)準(zhǔn)化

UI的搭建:xib VS 純代碼

蘋(píng)果一直用xib來(lái)標(biāo)榜他們家app開(kāi)發(fā)的簡(jiǎn)單易上手:將各種你需要的東西往屏幕上一拖一放缨叫,一個(gè)UI界面就搞定了椭符,這很cool不是嘛!

xib的優(yōu)點(diǎn)顯而易見(jiàn):

  • 易上手耻姥、可視化销钝,所見(jiàn)即所得
  • 減少代碼量
  • 快,適合小app快速開(kāi)發(fā)

但是在我們的實(shí)際項(xiàng)目中琐簇,是不推薦使用xib的蒸健,首先,xib本身過(guò)于笨拙婉商,只能搭建一些簡(jiǎn)單的UI似忧,動(dòng)態(tài)性很差,難以滿(mǎn)足app復(fù)雜的UI交互需求丈秩。

其次盯捌,做過(guò)性能優(yōu)化的同學(xué)都知道,xib(or StoryBoard)的性能是很差的蘑秽,相對(duì)于用純代碼alloc的組件來(lái)說(shuō)挽唉,xib加載慢,而且會(huì)占用app包的體積筷狼。不僅僅是app的性能瓶籽,使用老mac打開(kāi)較大的xib文件,有時(shí)候會(huì)卡的你懷疑人生埂材,嚴(yán)重影響開(kāi)發(fā)效率(心情)塑顺。

除此以外,對(duì)于團(tuán)隊(duì)協(xié)作來(lái)說(shuō),xib也不是一個(gè)好選項(xiàng):閱讀困難严拒,無(wú)法在git上查看歷史改動(dòng)扬绪,容易造成沖突,造成沖突后難以解決裤唠,元素通過(guò)outlets與代碼的鏈接難以維護(hù)挤牛,容易在改動(dòng)中造成錯(cuò)漏等等。

另外种蘸,對(duì)于我這種中途轉(zhuǎn)到前端的工程師來(lái)說(shuō)墓赴,對(duì)一切在IDE界面上配置的東西都有種迷之不信任,感覺(jué)不如一行行黑底白字的代碼來(lái)的靠譜航瞭。

當(dāng)然我們不是完全禁用了xib诫硕,用代碼碼UI的缺點(diǎn)也很明顯:繁瑣,代碼量大刊侯,因此對(duì)一些元素較多章办,又比較固定的UI組件,我們可以用xib來(lái)減少代碼量:

固定的UI組件可以使用xib

針對(duì)UI代碼繁瑣滨彻,重復(fù)編碼多的情況藕届,我們可以通過(guò)適當(dāng)封裝(UI工廠類(lèi)),組織結(jié)構(gòu)(MVC亭饵,分離UI代碼)等手段翰舌,清晰邏輯。

// label 工廠方法
+ (UILabel *)labelWithFont:(UIFont *)font
                     color:(UIColor *)
                      text:(NSString *)text
             attributeText:(NSAttributeString *)attributeText
                 alignment:(NSTextAlignment)alignment;

布局:返璞歸真

從iOS7開(kāi)始蘋(píng)果在Cocoa平臺(tái)引入AutoLayout進(jìn)行UI的基本布局冬骚,但是AutoLayout非常反人類(lèi),不僅代碼繁瑣而且使用不靈活限制很多懂算。

比如我想要把三個(gè)元素等間距地展示在屏幕上只冻,用AutoLayout寫(xiě)完基本蛋都碎了,更別說(shuō)動(dòng)態(tài)地在兩套布局間切換這種高級(jí)需求计技。

后來(lái)蘋(píng)果推出sizeClass喜德,試圖解決多套布局的問(wèn)題,但是仍然沒(méi)有觸及到碼農(nóng)的痛點(diǎn)垮媒,而且依賴(lài)xib使它泛用性不好舍悯。

看起來(lái)很美好的sizeClass

一段典型的AutoLayout代碼如下所示:

    _topViewTopPositionConstraint = [NSLayoutConstraint
                                     constraintWithItem:_topInfoView
                                     attribute:NSLayoutAttributeTop
                                     relatedBy:NSLayoutRelationEqual
                                     toItem:self.view
                                     attribute:NSLayoutAttributeTop
                                     multiplier:1.0
                                     constant:self.navigationController.navigationBar.frame.size.height + self.navigationController.navigationBar.frame.origin.y];
    
    [self.view addConstraint:topViewLeftPositionConstraint];
    
    (這里省略上述類(lèi)似結(jié)構(gòu)*4)

上面省略了很多代碼,實(shí)際上一頁(yè)都放不下睡雇,干了什么呢萌衬,只是將一個(gè)元素緊貼屏幕上邊緣放置。項(xiàng)目中我們會(huì)使用三方autoLayout的封裝:PureLayout 它抱,簡(jiǎn)化代碼秕豫,也有其它實(shí)用功能。

AutoLayout比較適合:

  • 基本的對(duì)齊(上下左右對(duì)齊,居中對(duì)齊等)
  • 固定的布局混移,固定的間距祠墅,動(dòng)態(tài)性不高的頁(yè)面
  • 簡(jiǎn)單且數(shù)量較少的UI元素

不擅長(zhǎng):

  • 比例布局
  • 動(dòng)態(tài)性較強(qiáng)的頁(yè)面局部
  • 不同屏幕大小比例的適配
  • 復(fù)雜的UI

另外有一點(diǎn),autoLayout對(duì)性能是有損耗的歌径,所以對(duì)性能有要求的場(chǎng)景怜森,比如列表中的cell,我們會(huì)用代碼計(jì)算frame盹廷,提高滑動(dòng)幀率跋炕。

所以在實(shí)際工程中,我們根據(jù)具體需要來(lái)選擇布局方式勺届。

下面是app中首頁(yè)新聞Feeds的布局代碼片段:

- (void)layoutSubviews {
    
    [super layoutSubviews];
    
    CGFloat cellWidth = CGRectGetWidth(self.bounds);
    CGFloat currentY = 0.f;
    
    // 0.content
    CGFloat cellHeight = CGRectGetHeight(self.bounds);
    CGFloat contentHeigth = cellHeight - kCellPaddingHeight;
    _mainContentView.frame = CGRectMake(0, 0, cellWidth, contentHeigth);
    
    // 1. topic
    CGFloat topicLabelWidth = [_topicLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_topicLabel.font} context:nil].size.width;
    
    CGFloat topicLabelHeight = [@"測(cè)高度" boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_topicLabel.font} context:nil].size.height;
    
    CGFloat topicLogoLeftPadding = 3.f;
    CGFloat topicLogoWidth = 10.f;
    CGFloat topicLeftPadding = 13.f;
    
    _topicView.frame = CGRectMake(topicLeftPadding, currentY + kTopicUpPadding, topicLogoWidth + topicLogoLeftPadding + topicLabelWidth, topicLabelHeight);
    _topicLogo.frame = CGRectMake(topicLabelWidth + topicLogoLeftPadding, CGRectGetHeight(_topicView.frame) / 2.0 - topicLogoWidth / 2.0, topicLogoWidth, topicLogoWidth);
    _topicLabel.frame = CGRectMake(0, 0, topicLabelWidth, topicLabelHeight);
    
    (省略大量代碼……)
    
    // 10._sourceLabel
    CGSize sourceSize = [_sourceLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_sourceLabel.font} context:nil].size;
    
    _sourceLabel.frame = CGRectMake(kEdgeHorizontalPadding, currentY + kLeadingUpPading, sourceSize.width, sourceSize.height);
}

可以看到驶俊,為了確定每個(gè)元素的位置,我們需要進(jìn)行大量的計(jì)算免姿,代碼可讀性也不好饼酿,繁瑣難讀。如果引入動(dòng)態(tài)性胚膊,比如不同屏幕字體大小改變故俐,元素大小按比例擴(kuò)大等,則計(jì)算量又要上一個(gè)數(shù)量級(jí)紊婉。

動(dòng)態(tài)布局:清晰獨(dú)立

UI界面是動(dòng)態(tài)的药版,在不同狀態(tài),不同尺寸或者手機(jī)的橫豎屏情況下喻犁,我們往往需要在多套布局方案中切換槽片,或者對(duì)布局進(jìn)行微調(diào)。如果使用xib布局的話肢础,可以使用SizeClass+AutoLayout的方案还栓;如果是代碼實(shí)現(xiàn)的頁(yè)面,則沒(méi)有官方提供的工具传轰,只能用邏輯去判斷剩盒。

一般來(lái)說(shuō),我們寫(xiě)復(fù)雜的UI頁(yè)面慨蛙,需要遵循兩個(gè)原則:

  • UI布局代碼辽聊,要清晰:這是最重要的,要一眼就知道在調(diào)整那一塊期贫,怎么調(diào)整跟匆,如果不能,適當(dāng)拆分通砍,優(yōu)化命名贾铝。
  • 布局代碼要和業(yè)務(wù)邏輯獨(dú)立:在一些常用設(shè)計(jì)模式下,我們會(huì)將UI和數(shù)據(jù)模型解耦,在UI內(nèi)部垢揩,同樣要將交互玖绿,配置這些邏輯和布局解耦,獨(dú)立出類(lèi)似前端css這樣的純布局文件叁巨。

將布局代碼提煉出來(lái)斑匪,在不同尺寸下調(diào)用不同的實(shí)現(xiàn):

    if (IS_IPHONE_6){  
        self.layout = [MyLayout iPhone6Layout];
    }else if (IS_IPHONE_6_PLUS){  
        self.layout = [MyLayout iPhone6PlusLayout]; 
    }

    // 實(shí)現(xiàn)小屏幕布局
    + (MyLayout *)iPhone6Layout {...}
    // 實(shí)現(xiàn)大屏幕布局
    + (MyLayout *)iPhone6PlusLayout {...}

字體適配:字體集

在開(kāi)發(fā)中我們經(jīng)常會(huì)遇到需要?jiǎng)討B(tài)設(shè)置字體的情況:

  • 不同屏幕尺寸,或者橫豎屏锋勺,需要展示不同的字體大小蚀瘸。
  • 為用戶(hù)提供了文章調(diào)節(jié)字體選項(xiàng)。
  • App的不同語(yǔ)言版本庶橱,需要顯示的字體不一樣贮勃。
字體大小調(diào)節(jié)

較為簡(jiǎn)單的做法是用宏或者枚舉定義字體參數(shù),針對(duì)不同尺寸的屏幕苏章,我們拿到不同的值:

#ifdef IPHONE6
#define kChatFontSize 16.f
#else IPHONE6Plus
#define kChatFontSize 18.f
#endif

在對(duì)一些舊代碼做字體適配擴(kuò)展的時(shí)候寂嘉,直接修改源碼改動(dòng)太多,容易混亂枫绅,可以采用runTime方法hack Label等控件的展示泉孩,替換原有的setFont方法:

+ (void)load{  
    
    Method newMethod = class_getClassMethod([self class], @selector(mySystemFontOfSize:));  
    Method method = class_getClassMethod([self class], @selector(systemFontOfSize:));  
    method_exchangeImplementations(newMethod, method);  
}  
  
+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize{  
    UIFont *newFont=nil;  
    if (IS_IPHONE_6){  
        newFont = [UIFont adjustFont:fontSize * IPHONE6_INCREMENT];  
    }else if (IS_IPHONE_6_PLUS){  
        newFont = [UIFont adjustFont:fontSize * IPHONE6PLUS_INCREMENT];  
    }else{  
        newFont = [UIFont adjustFont:fontSize];  
    }  
    return newFont;  
}  

以上套路缺點(diǎn)顯而易見(jiàn):不夠靈活,將邏輯分散并淋,不便于維護(hù)寓搬,擴(kuò)展性也不好。

一種比較好的實(shí)踐是引入字體集(Font Collection)的概念县耽,什么是字體集呢句喷,我們?cè)谟肒eynote或者Office的時(shí)候,軟件會(huì)提供一些段落樣式兔毙,定義了段落唾琼、標(biāo)題、說(shuō)明等文字的字體瞒御,我們可以在不同的段落樣式中切換,來(lái)直接改變整個(gè)文章的字體風(fēng)格神郊。

Keynote中的字體集

聽(tīng)上去和我們的需求是不是很像呢肴裙,我們?cè)诖a中也是做類(lèi)似的事情,將不同場(chǎng)景下的字體定義到一個(gè)Font Collection中:

@protocol XRFontCollectionProtocol <NSObject>

- (UIFont *)bodyFont; // 文章
- (UIFont *)chatFont; // 聊天
- (UIFont *)titleFont; // 標(biāo)題
- (UIFont *)noteFont; // 說(shuō)明
......
@end

不同的場(chǎng)景涌乳,靈活選擇不同的字體集:

+ (id<XRFontCollectionProtocol>)currentFontCollection {
    
#ifdef IS_IPhone6
    return [self collectionForIPhone6];
#elif IS_IPhone6p
    return [self collectionForIPhone6Plus];
#endif
    return nil;
}

// set font
titleLabel.font = [[XRFontManager currentFontCollection] titleFont];

適配新的屏幕或者場(chǎng)景蜻懦,我們只需要簡(jiǎn)單地增加一套字體集就好了,可以很方便的管理app中的字體樣式夕晓,做動(dòng)態(tài)切換也很簡(jiǎn)單宛乃。

總結(jié)來(lái)說(shuō),用代碼在一個(gè)尺寸實(shí)現(xiàn)設(shè)計(jì)稿是比較簡(jiǎn)單的,但是要在各種尺寸下忠實(shí)反應(yīng)設(shè)計(jì)的想法需要合理的代碼設(shè)計(jì)以及一定的代碼量征炼。

UI的還原其實(shí)也是大前端開(kāi)發(fā)非常重要的部分析既,作為程序員,往往重視代碼的穩(wěn)定谆奥,業(yè)務(wù)的正常使用而忽略軟件界面這個(gè)同樣重要的用戶(hù)體驗(yàn)因素眼坏。設(shè)身處地地想,如果設(shè)計(jì)看到自己精心調(diào)配的比例酸些、字體宰译、色號(hào)在不同尺寸手機(jī)上顯示得歪七倒八,一定會(huì)氣的要死吧哈哈哈哈哈魄懂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沿侈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子市栗,更是在濱河造成了極大的恐慌缀拭,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肃廓,死亡現(xiàn)場(chǎng)離奇詭異智厌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)盲赊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)铣鹏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人哀蘑,你說(shuō)我怎么就攤上這事诚卸。” “怎么了绘迁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵合溺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缀台,道長(zhǎng)棠赛,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任膛腐,我火速辦了婚禮睛约,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哲身。我一直安慰自己辩涝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布勘天。 她就那樣靜靜地躺著怔揩,像睡著了一般捉邢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上商膊,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天伏伐,我揣著相機(jī)與錄音,去河邊找鬼翘狱。 笑死秘案,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的潦匈。 我是一名探鬼主播阱高,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茬缩!你這毒婦竟也來(lái)了赤惊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凰锡,失蹤者是張志新(化名)和其女友劉穎未舟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體掂为,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裕膀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勇哗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昼扛。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖欲诺,靈堂內(nèi)的尸體忽然破棺而出抄谐,到底是詐尸還是另有隱情,我是刑警寧澤扰法,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布蛹含,位于F島的核電站,受9級(jí)特大地震影響塞颁,放射性物質(zhì)發(fā)生泄漏浦箱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一祠锣、第九天 我趴在偏房一處隱蔽的房頂上張望酷窥。 院中可真熱鬧,春花似錦锤岸、人聲如沸竖幔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拳氢。三九已至,卻和暖如春蛋铆,著一層夾襖步出監(jiān)牢的瞬間馋评,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工刺啦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留留特,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓玛瘸,卻偏偏與公主長(zhǎng)得像蜕青,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糊渊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344