iOS架構(gòu)設(shè)計(解耦的嘗試)之UI樣式復(fù)用與布局管理

抽象的威力

從小老師就耳提面命:要看透問題的表象看本質(zhì)。起初不解而涉,隨著干Coder時間長了著瓶,愈發(fā)察覺要想偷懶就得去解決本質(zhì)問題,而不是圍繞著問題的表層轉(zhuǎn)來轉(zhuǎn)去啼县。而進(jìn)一步說材原,不能停留在使用簡單抽象出來的概念解決問題的層次,應(yīng)該進(jìn)一步使用一些高級抽象季眷。就拿布局這個事情來說吧余蟹。

原始抽象

第一層的抽象是對現(xiàn)實世界在數(shù)字世界的描述,用坐標(biāo)系統(tǒng)子刮。用數(shù)學(xué)語言來描述布局這個事情威酒。其實抽象到這一步也是一個非常大的進(jìn)步了。在iOS中也就是我們常用的spring&struct的布局系統(tǒng)话告。通過frame等來標(biāo)注一個view來控制平面布局受神,通過View-Tree來控制Z軸方向上的布局犹褒。

基于這種抽象的情況下勇皇,我們做的事情就是精準(zhǔn)的描述每一個View在坐標(biāo)系統(tǒng)中的位置嗡靡。這個就是那個繁重的工作。在layoutsubviews函數(shù)里面里面病线,各種計算大小和偏移量吓著。熟悉iOS的同學(xué),隨意看一下自己View里面layoutSubviews函數(shù)的大小就可以感受到其繁重送挑。

描述抽象

抽象這個東西有意思的地方就在于他是可以遞歸使用的绑莺。在第一層抽象得到的概念基礎(chǔ)上,進(jìn)行第二層抽象惕耕;在第二層抽象得到的概念基礎(chǔ)上纺裁,進(jìn)行第三層抽象…..如此反復(fù)。當(dāng)然司澎,Apple牛逼的工程師們也會意識到spring&struct的繁重欺缘,于是就有了Autolayout(自動布局系統(tǒng))。

Autolayout是基于描述的抽象挤安,更多的是描述元素與元素之間的位置關(guān)系谚殊。通過解n元一次方程組來確定元素的位置(在坐標(biāo)系統(tǒng)中的frame)。而這種基于模式的抽象蛤铜,要比剛才第一層的抽象要高級很多嫩絮。在這種抽象概念下丛肢,我們發(fā)現(xiàn)我們寫的布局的代碼有了一個大幅度的減少。原先需要拼了老命計算view frame的情況變成了剿干,描述view之間關(guān)系的過程蜂怎。原先需要非常多代碼才能完成的任務(wù),現(xiàn)在只要簡單的寫一句AView在x軸方向上距離BView固定間隔為10就可以完成了置尔。隨著代碼量的降低派敷,我們的對于布局系統(tǒng)理解成本都在降低。

復(fù)雜性由固有復(fù)雜性和認(rèn)知復(fù)雜性組成撰洗。

而基于描述的抽象,因為是對第一層抽象概念的操作腐芍,忽略了第一層抽象的很多細(xì)節(jié)差导,很大程度上降低了布局系統(tǒng)的認(rèn)知復(fù)雜度。無論從代碼量上還是我們的認(rèn)知成本上猪勇,都讓我們可以『偷懶』了设褐。

任務(wù)抽象

而有了基于描述抽象的Autolayout之后,我們會發(fā)現(xiàn)其實很多任務(wù)還是很繁重泣刹,比如我們要寫個List布局助析,不可能每次都把List中每個元素的位置關(guān)系描述一邊啊。比如我們要寫個Collection的布局椅您,也不可能每次都把每個元素的位置關(guān)系都描述一邊啊…..

于是真對某些特定任務(wù)外冀,會使用任務(wù)抽象。把List布局抽象成UITableView掀泳,把Collection布局抽象成UICollectionView雪隧,把線性布局抽象成UIStackView。员舵。脑沿。。马僻。而且Apple的工程師們也會在這條路上越走越遠(yuǎn)庄拇。在近幾個版本的iOS-SDK更新中,我們也看到了更多的這種布局View的出現(xiàn)韭邓。相信以后也會更多措近。

而這種類型的View在簡化編程工作這件事情上要比autolayout來的更加厲害∪猿樱可以隨意感受一下熄诡,我原先為了實現(xiàn)一個TableView所寫的代碼量DZTableView。就知道當(dāng)我們把通用任務(wù)抽象出來的時候诗力,能偷多大的懶了凰浮。

而關(guān)于這種任務(wù)抽象我們聽到的最多就是面試中經(jīng)常被問及的GoF設(shè)計模式我抠。在想想應(yīng)用設(shè)計模式時的爽,也就知道抽象這個工具的確很好用袜茧。在這里強烈推薦一本書《元素模式》菜拓。個人認(rèn)為這本提供了一整套的瑞士軍刀,來幫你進(jìn)行抽象或者去設(shè)計『設(shè)計模式』笛厦。

….

當(dāng)然這里還會存在更高層次的抽象纳鼎。不過嘛,抽象并非銀彈裳凸。并不是說一味的抽象下去就能夠得到極致的『偷懶』贱鄙。隨著抽象層次提高,概念密度也在提高姨谷,而那些在這個過程中所忽略的特殊場中的細(xì)節(jié)逗宁,將變得難以還原。有些時候梦湘,處理問題反而更加困難瞎颗,編碼量卻在增加。老生常談的問題鞍埔椤:度哼拔。當(dāng)抽象層次滿足業(yè)務(wù)需求和業(yè)務(wù)發(fā)展的時候,就可以臨時先止步了瓣颅。

DZGeometryTools

github地址:https://github.com/yishuiliunian/DZGeometryTools
主要是用了描述抽象和任務(wù)抽象兩種技術(shù)倦逐。看起來忽悠人弄捕,其實實現(xiàn)部分一看就明白了僻孝。無非是將常用的一些任務(wù)轉(zhuǎn)換成了函數(shù)而已。
其實在CoreGraphics框架中又一個CGGeometry.h文件守谓,其中提供了很多方便操作CGRect等幾何類型的函數(shù)穿铆。而在DZGemetryTools中所做的工作可以看做是對CGGeometry的一個擴展。

DZGeometryTools.h中主要是對幾何類型的操作

通過抽象我將比較常見的幾何操作歸結(jié)為以下幾種比較基礎(chǔ)的操作:
1.偏移操作
2.縮放操作
3.間距計算操作
4.margin施加操作
現(xiàn)在把他們提取出來斋荞,基本上就是完成了一個工具集的構(gòu)建荞雏。而后在這些工具集上,就可以來描述每個Rect之間的關(guān)系平酿,這個有點類似于autolayout凤优,不過是提前收工算好了,而autolayout是自動計算并賦值的蜈彼。貼段真實使用中代碼來感受一下:

- (void) layoutSubviews
{
    [super layoutSubviews];
    CGSize imageSize = {62*2, 84};
    CGRect contentRect = CGRectCenterSubSize(self.bounds, CGSizeMake(20, 20));
    CGRect textRect;
    CGRect imageRect;
    
    CGRectDivide(contentRect, &textRect, &imageRect, 30, CGRectMaxYEdge);
    imageRect = CGRectCenter(imageRect, imageSize);
    
    CGRect imgRs[2];
    CGRectHorizontalSplit(imageRect, imgRs, 2, 0);
    _indicatorImageView.frame = imgRs[0];
    _powerImageView.frame = imgRs[1];
    _textLabel.frame = textRect;
    _backgroundView.frame = self.bounds;
    _lastTimeLabel.frame = imageRect;
}

其中使用到了CGRectCenterSubSize 來對contentRect做了margin計算筑辨,并用CGRectHorizontalSplit 對rect坐了縱向均分的操作,這些都是描述UI元素之間相對位置關(guān)系的關(guān)系幸逆,我們通過手工計算來完成了對于UI元素的布局操作棍辕。這些函數(shù)都沒有采用直接操作view.frame的方式暮现,只進(jìn)行了幾何運算,計算出了UI元素坐標(biāo)楚昭。之所以采取這種方式栖袋,是因為想基于目前的抽象層次應(yīng)該是針對于幾何概念的操作。對于UI元素的操作抚太,已經(jīng)簡化成了一個賦值操作塘幅,沒有太大必要去優(yōu)化處理了。而相較于以前的編碼量和思維量來說尿贫,基于目前構(gòu)建的這個工具集來進(jìn)行編碼已經(jīng)省了不少力氣了电媳。體力活少了。

DZLayoutMacros.h 常用的任務(wù)

在這個文件里面依舊是一些常用的布局工具集庆亡,不過和上面不一樣的是采用了另外的表達(dá)方式:宏匆背。針對于一些比較簡單的任務(wù),做了抽象身冀。比如:

1.y依賴于頂部元素,并且盡可能填充滿width的布局
2.頂部固定高度括享,鋪滿width的布局
3.//底部固定高度搂根,鋪滿width的布局
4.…..

StyleSheet

github地址:https://github.com/yishuiliunian/StyleSheet
據(jù)說一個終端開發(fā)人員將會有70%以上的時間在和UI打交道。自己想想也對铃辖,貌似有很大一部分時間花費在了調(diào)整UI樣式剩愧,addSubView還有l(wèi)ayout上面。猛然間就發(fā)現(xiàn)自己的代碼中有大量這種東西存在

self.label.layer.cornerRadius = 3;
    self.label.textColor = [UIColor darkTextColor];
    self.label.font = [UIFont systemFontOfSize:13];
    self.label.backgroundColor = [UIColor greenColor];
    self.label.layer.borderWidth = 2;
    self.label.layer.borderColor = [UIColor redColor].CGColor;


    self.label2.layer.cornerRadius = 3;
    self.label2.textColor = [UIColor darkTextColor];
    self.label2.font = [UIFont systemFontOfSize:13];
    self.label2.backgroundColor = [UIColor greenColor];
    self.label2.layer.borderWidth = 2;
    self.label2.layer.borderColor = [UIColor redColor].CGColor;


    self.button.layer.cornerRadius = 3;
    self.button.backgroundColor = [UIColor greenColor];
    self.button.layer.borderWidth = 2;
    self.button.layer.borderColor = [UIColor redColor].CGColor;

    self.aView.layer.cornerRadius = 3;
    self.aView.backgroundColor = [UIColor greenColor];
    self.aView.layer.borderWidth = 2;
    self.aView.layer.borderColor = [UIColor redColor].CGColor;
    ......
    

上面的代碼是為了實現(xiàn)這樣的效果而寫的代碼娇斩。

很多幾乎是一毛一樣的代碼仁卷,充斥著整個APP。自己花在這些樣式調(diào)整上的時間也非常多犬第。為了實現(xiàn)一個樣式效果锦积,需要配置各種各樣的屬性。而且很多界面中這些樣式都是一樣的歉嗓。于是又是無數(shù)次的重復(fù)上面的工作丰介。oy my god!時間啊鉴分,就這樣流走了哮幢。做為一個懶人,就會發(fā)問有沒有一種可以少寫點代碼的方式呢志珍?你可以寫一個子類嘛橙垢,但是會有類污染的問題,單純?yōu)榱艘粋€公有樣式伦糯,就創(chuàng)建個子類有點大材小用柜某。那寫一批樣式渲染的函數(shù)唄嗽元,恩這個注意不錯,但是細(xì)想一下工作量也不小莺琳,而且不通用还棱。于是,花了幾天的時間我寫了StyleSheet這個庫惭等。為了的就是來簡化UI樣式的編碼珍手。

通過上述描述我們可以發(fā)現(xiàn),原始的寫UI樣式的問題:

1.繁瑣的代碼辞做,大量重復(fù)性的工作
2.樣式無法共享琳要,每一個View都需要重新進(jìn)行樣式賦值。

而StyleSheet的設(shè)計目標(biāo)就是:

1.樣式配置輕便化秤茅,能夠使用更加少的代碼來描述View的樣式
2.樣式在View之間的共享.不止是相同類的實例之間的共享稚补,甚至是跨類的共享。
So框喳,先看看上述代碼使用StyleSheet之后的效果:

self.label.style = 
DZLabelStyleMake(
  style.backgroundColor = [UIColor greenColor];
  style.cornerRedius = 3;
  style.borderColor = [UIColor redColor];
  style.borderWidth = 2;
  style.textStyle.textColor = [UIColor darkTextColor];
  style.textStyle.font = [UIFont systemFontOfSize:13];
);
self.label2.style = self.label.style;
self.aView.style = self.label.style;
[self.button.style copyAttributesWithStyle:self.label.style];

設(shè)計

基礎(chǔ)抽象模型很簡單课幕,就是要讓界面上關(guān)于展示的屬性可以被組合使用。而我們所謂的樣式五垮,其實也就是各種基礎(chǔ)屬性組合出來的結(jié)果乍惊。基于這個模型放仗,在設(shè)計StyleSheet的時候故意淡化了被渲染的View的類型的概念润绎,任何一種類型的Style可以對任何類型的View進(jìn)行渲染,但是必須是這種類型的View支持Style所指稱的屬性诞挨。比如你可以使用真對Button設(shè)計的DZButtonStateStyle來渲染一個UILabel莉撇,但由于UILabel不支持DZButtonStateStyle中的渲染屬性,所以渲染結(jié)果是無效的惶傻。

但是當(dāng)使用DZButtonStyle(繼承自DZViewStyle)來渲染UILabel的時候棍郎,會使用DZButtonStyle中其父類的某些渲染屬性,來渲染UILabel的父類UIView所支持的那些屬性银室。

使用

直接使用Style對View進(jìn)行渲染:

DZLabelStyle* style =
DZLabelStyleMake(
    style.backgroundColor = [UIColor greenColor];
    style.cornerRedius = 3;
    style.borderColor = [UIColor redColor];
    style.borderWidth = 2;
    style.textStyle.textColor = [UIColor darkTextColor];
    style.textStyle.font = [UIFont systemFontOfSize:13];
);

[style decorateView:self.label];

直接渲染的好處是坝撑,不用再次生成Style對象,更加方便樣式在多個View之間渲染粮揉。
賦值渲染
對UIKit中常用的一些組件進(jìn)行了擴張為他們增利了style屬性巡李,直接進(jìn)行style屬性的賦值,會出發(fā)一次渲染操作扶认。當(dāng)?shù)谝淮握{(diào)用style屬性的時候侨拦,會自動生成一個zeroStyle并賦值。

self.label.style = style

或者

self.label.style = DZLabelStyleMake( 
      style.backgroundColor = [UIColor greenColor]; 
      style.cornerRedius = 3;
      style.borderColor = [UIColor redColor]; 
      style.borderWidth = 2;                          
      style.textStyle.textColor = [UIColor darkTextColor];      
      style.textStyle.font = [UIFont systemFontOfSize:13]; 
); 

當(dāng)進(jìn)行賦值渲染的時候辐宾,會將Style的Copy后的實例與當(dāng)前View綁定狱从,當(dāng)更改Style的屬性的時候膨蛮,對應(yīng)View的樣式會立刻改變。

通用樣式的共享

使用原有的配置季研,進(jìn)行通用樣式的共享是個非常困難的事情敞葛,基本上都是體力活,靠人力來維護(hù)与涡。我們的代碼中會摻雜大量的用于配置樣式的代碼惹谐,而且是獨立且散在。 現(xiàn)在你可以通過StyleSheet解決: 定義共享的樣式:

//在頭文件中使用 xxx.h 聲明一個公有樣式
EXTERN_SHARE_LABEL_STYLE(Content)

//在實現(xiàn)文件中使用 xxx.m 驼卖,實現(xiàn)一個公有樣式
IMP_SHARE_LABEL_STYLE(Content,
   style.backgroundColor = [UIColor clearColor];
   style.cornerRedius = 2;
   style.textStyle.textColor = [UIColor redColor];
)

(1)使用共享樣式,方式一 self.label.style = DZStyleContent();
(2)使用共享樣式氨肌,方式二(推薦) 很多時候, 如果不需要進(jìn)一步更改樣式,可以不采復(fù)制賦值的方式來進(jìn)行渲染,可以直接使用: [DZStyleContent() decorateView:self.label]; 只進(jìn)行渲染酌畜,而不進(jìn)行復(fù)制怎囚。
好了,現(xiàn)在可以嘗試著換這種方式來寫UI樣式了桥胞。

BY:yishuiliunian

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恳守,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贩虾,更是在濱河造成了極大的恐慌井誉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件整胃,死亡現(xiàn)場離奇詭異,居然都是意外死亡喳钟,警方通過查閱死者的電腦和手機屁使,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奔则,“玉大人蛮寂,你說我怎么就攤上這事∫撞纾” “怎么了酬蹋?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抽莱。 經(jīng)常有香客問我范抓,道長,這世上最難降的妖魔是什么食铐? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任匕垫,我火速辦了婚禮,結(jié)果婚禮上虐呻,老公的妹妹穿的比我還像新娘象泵。我一直安慰自己寞秃,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布偶惠。 她就那樣靜靜地躺著春寿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忽孽。 梳的紋絲不亂的頭發(fā)上绑改,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音扒腕,去河邊找鬼绢淀。 笑死,一個胖子當(dāng)著我的面吹牛瘾腰,可吹牛的內(nèi)容都是我干的皆的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蹋盆,長吁一口氣:“原來是場噩夢啊……” “哼费薄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起栖雾,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤楞抡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后析藕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡治泥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡澎灸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情其兴,我是刑警寧澤守问,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏常柄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一相种、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧品姓,春花似錦寝并、人聲如沸箫措。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斤蔓。三九已至,卻和暖如春镀岛,著一層夾襖步出監(jiān)牢的瞬間弦牡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工漂羊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驾锰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓走越,卻偏偏與公主長得像椭豫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子买喧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,133評論 25 707
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案捻悯? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補...
    _Yfling閱讀 13,753評論 1 92
  • 開心的時候去看《欲望都市》吧。傷心的時候也去看《欲望都市》吧淤毛。 大學(xué)的時候今缚,這部劇正火,但是當(dāng)時只匆匆看了一會低淡,就...
    FAN心理閱讀 601評論 1 2
  • 字符串轉(zhuǎn)換整數(shù)需要注意下面四個問題姓言,尤其需要注意關(guān)于溢出的處理。字符串是否為空是否包含正負(fù)號是否包含其它字符是否溢...
    鬼谷神奇閱讀 300評論 0 0
  • 系辭上 第一章 天尊地卑蔗蹋,乾坤定矣何荚。卑高以陳,貴賤位矣猪杭。動靜有常餐塘,剛?cè)釘嘁印7揭灶惥墼硭保镆匀悍纸渖担獌瓷印T谔斐上?..
    田園讀書人閱讀 853評論 4 8