一、前言
一個(gè)App發(fā)展到后期,沒(méi)有幾套主題,完全拿不出手啊。特別是toB的公 司,客戶要求自己的個(gè)性更是正常不過(guò)的需求了。換膚是個(gè)細(xì)活、細(xì)活、細(xì)活,重要的事說(shuō)三遍,方案設(shè)計(jì)也是相當(dāng)關(guān)鍵。
二、方案對(duì)比
2.1 換膚要處理的問(wèn)題
1.配置抽離。
2.已存在界面的動(dòng)態(tài)變換。
2.2 分析
針對(duì)第一點(diǎn),確實(shí)沒(méi)太多說(shuō)道,主要是和UI撕抽取公共配置和細(xì)節(jié)配置,這是一個(gè)細(xì)活。而界面的動(dòng)態(tài)變換,這是一個(gè)麻煩事。
2.2.1 通知切換主題
這個(gè)事我們首先想到的就是通知,但是用通知太麻煩了,監(jiān)聽(tīng)移除一個(gè)改變寫(xiě)兩地。而且一個(gè)大型app,很多view其實(shí)并沒(méi)有屬性引用,使用通知又需要把這些view屬性引用出來(lái),無(wú)形中增加了很多工作量。既然已經(jīng)有通知保底了,我們何不嘗試一下其它方案。
2.2.2 RX OR RAC
針對(duì)上面所說(shuō)關(guān)于通知的兩個(gè)問(wèn)題,我們使用 ReactiveCocoa 或 RXSwift 來(lái)優(yōu)雅的處理這個(gè)問(wèn)題。不過(guò)大部分項(xiàng)目并沒(méi)有使用鏈?zhǔn)骄幊?,為了換膚導(dǎo)入貌似不太劃算。
2.2.3 使用block來(lái)處理
在項(xiàng)目中建一個(gè)單例來(lái)管理配置,將所有顏色配置代碼塊放入block中并加入到單例的數(shù)組中管理,在切換主題時(shí),遍歷數(shù)組執(zhí)行一遍block即可。當(dāng)然使用時(shí)要注意block引用問(wèn)題,保證視圖的正常釋放,隨著app的使用block會(huì)越來(lái)越多,要及時(shí)清理已釋放view的block。
三、block方案的實(shí)現(xiàn)
寫(xiě)了個(gè)小Demo具體可看 github -ArtChangeTheme 下面做一些簡(jiǎn)單的講解。
3.1 配置的模塊劃分
換膚是個(gè)細(xì)活,項(xiàng)目過(guò)大就需要進(jìn)行模塊劃分,每個(gè)人負(fù)責(zé)自己的模塊,看似麻煩的事分到個(gè)人也就不多了。Demo寫(xiě)了 Module1到Module4 四個(gè)模塊,每個(gè)模塊下都建了一個(gè)UIStyle文件夾用于管理當(dāng)前模塊的配置。公共UIStyle則和模塊目錄同級(jí)。通過(guò)分類擴(kuò)展。
- (NSString *)getStyleName_Module1;
每個(gè)模塊都要寫(xiě)該方法,用于返回本模塊使用的配置文件plist。
方法的命名規(guī)則是 getStyleName_模塊名。 以此解耦,解耦思路是參考
測(cè)試用例執(zhí)行所有 以test開(kāi)頭的方法,為此寫(xiě)了個(gè)分類NSObject+ArtPrefix。
3.2 配置的解析

如上圖模塊下配置分為 Image 與 Style,Image為圖片配置,
3.2.1 圖片的配置
toPath表示圖片所在的相對(duì)文件夾,imageConfigs里面包含了需要?jiǎng)討B(tài)配置的文件名以及備注,這是一個(gè)規(guī)范,我們可以很清楚的看到處理了哪些圖片,如果打包動(dòng)態(tài)替換主題,也可通過(guò)該配置去替換需要的圖片。
3.2.2 Style樣式配置
color:000000,0.5 前面表示 Hex設(shè)置 0.5表示透明度。
font: 表示字體大小。
還有 ArtLayoutInfo 類對(duì)應(yīng)的屬性 是用于描述約束關(guān)系配置的,比如某些客戶可能對(duì) banner 的寬高比有所要求,這個(gè)可以在這做擴(kuò)展配置。
3.3 block方案接口設(shè)計(jì)
- (void)saveStrongSelf:(id)strongSelf block:(void(^)(id weakSelf))aBlock;
如上:考慮代碼的調(diào)用方便,和block及時(shí)的清理標(biāo)記,設(shè)計(jì)了如上的調(diào)用接口,這樣大家不用寫(xiě)__weak typeof(self) weakSelf = self,這么麻煩,但是如果block內(nèi)有用非weakSelf. 調(diào)用的view請(qǐng)注意弱引用聲明。
ArtUIStyleManager內(nèi)部開(kāi)啟了定時(shí)器(默認(rèn)60s可設(shè)置clearInterval控制間隔)去檢測(cè)清理無(wú)用的block。實(shí)現(xiàn)原理是將 strongSelf 弱引用存儲(chǔ),檢測(cè)其被釋放后刪除對(duì)應(yīng)的block。至于弱引用存儲(chǔ)的實(shí)現(xiàn)大家可以看看 iOS - 如何實(shí)現(xiàn)弱引用字典,我這里使用的是block封裝與解封。這種小技巧平時(shí)用不到,關(guān)鍵時(shí)刻還是能幫你一把的。
- (void)reloadStylePath:(NSString *)aStylePath;
- (void)reloadStyleBundleName:(NSString *)aStyleBundleName;
- (void)reloadStyleBundle:(NSBundle *)aStyleBundle;
這三個(gè)方法是用于加載其它樣式的,具體可看demo中ArtModule4ViewController。
四、幾個(gè)小技巧
4.1 能用Color解決的就別麻煩UI出新圖了
很多UI控件都有個(gè) tintColor 屬性,很多圖片是純色的,大家直接通過(guò)tintColor渲染就好了。Demo也提供了一個(gè)分類UIImage+ArtDraw來(lái)進(jìn)行渲染繪制。不過(guò)我遇到了一個(gè)坑,我把渲染的圖做了拉伸,點(diǎn)擊了返回時(shí),顏色還原成圖片本身的顏色。大家使用時(shí)注意一下即可。
4.2 iOS 8后系統(tǒng)自動(dòng)會(huì)將@3x圖片自動(dòng)適配圖片
如題,大家搜一下即可,也就是說(shuō)來(lái)張@3x即可,要適配iOS8 一下的話,自己用代碼切一下也是沒(méi)問(wèn)題的。不過(guò)雖說(shuō)系統(tǒng)自動(dòng)適配,但是我在1x的老iPad上出現(xiàn)了圖片虛化有毛邊的情況,加了1x圖就好很多,這個(gè)大家自己看著辦。
4.3 為什么不用iconfont呢
在iOS開(kāi)發(fā)中使用iconfont圖標(biāo)
為什么不用呢?唉,長(zhǎng)嘆一聲。
五、來(lái)個(gè)HotReloader

換膚這個(gè)事,最蛋疼的應(yīng)該是,改一點(diǎn)跑一次代碼,這個(gè)耗時(shí)太久了,加一個(gè)小插件ArtUIStyleHotReloader 修改之后,command+s ,就能動(dòng)態(tài)變了,當(dāng)然只能模擬器使用。代碼很簡(jiǎn)單,感謝VKCssProtocol。
六、結(jié)尾
2017.08.28,今天是個(gè)好日子,但是出了點(diǎn)事,心情不太好,這篇文章也就簡(jiǎn)單的寫(xiě)寫(xiě)了。這個(gè)OC在原有的一些上面擴(kuò)展的ArtUIStyle看著可能有些臃腫。改天抽空寫(xiě)個(gè)swift版的,還是這個(gè) github -ArtChangeTheme。趕緊去github給個(gè)小星星吧。
換膚這個(gè)事,UI自己要有套設(shè)計(jì)規(guī)范,如果前期沒(méi)有規(guī)范不要緊,做個(gè)換膚看他還有沒(méi)規(guī)范,再?zèng)]規(guī)范大家就GG了。最后來(lái)個(gè)擴(kuò)展閱讀 VKCssProtocol。