遷移一批老文章到掘金
CSS產生的想法
早先,寫過一陣子RN,前一陣子寫微信小程式,深深地覺得CSS這個東西寫起來很爽,樣式與介面完全隔離,寫好一套一套的樣式CSS Class
然後,在寫介面HTML的時候直接對介面元素,無論是什麼HTML標籤,什麼控制元件,只要指定CSS Class
的名字就能自動生效。
- 樣式和介面完全隔離解耦
- 樣式之間可以自由任意排列組合建立新CSS Class
- 對任意介面元素指定樣式Class名就能自動生效
客戶端場景
- UI出App設計圖也會有一套標準UI庫
- 不同的底色,字號,字色,圓角,字型,陰影等等樣式屬性相互組合
- UI有時候更換整體設計風格的時候,所有專案中用到的標準元件,應該隨著UI設計一起變為最新的設計效果
- 標準UI庫擴充套件延伸一下,就是客戶端主題風格,換膚等系統
工廠模式
看到上面的設計需求,相信很多人第一時間想到的都是客戶端裡構建一套工廠模式
- 由工廠模式統一生成UI設計的標準控制元件
- 所有需要使用標註控制元件的地方都用工廠去生成
- 當UI需要整體修改樣式屬性,修改工廠模式的構造方法就能實現整體應用新效果
我不是很喜歡這種模式
- 寫法不統一,必須讓使用者使用工廠的構造方法來建立標準UI,非標準UI寫法就隨意了
- 耦合性比較強,必須引入工廠模組
Css樣式,Protocol協議
- 希望引入Css的樣式思想,讓樣式與介面分離
- 將UI給的統一標準設計圖,專心的寫成
.css
檔案 - 可以動態下發,可以動態替換,動態更新效果。
- 希望像iOS Protocol協議那樣工作
- 不管控制元件是Label Button Image View,CSSClass都可以直接指定一個協議名字,樣式功能就自動生效
- 不管控制元件是用工廠建立的,還是Xib拖線的,還是手寫程式碼寫的,都能無縫接入這個cssprotocol
xxview.protocol = "cssclassname1 cssclassname2"
這種感覺- 協議可以同時指定多個樣式,以空格區分,樣式之間自由組合
我不想動態修改介面元素佈局,動態建立全新的介面
大家都知道,CSS有一個很大的用途就是用於介面佈局,並且css的佈局寫法和iOS原生的佈局寫法有很大的區別,所以在這裡我想強調一點,我這裡寫的cssprotocol,不想包含任何跟佈局有關,只是單純的動態配置外觀樣式,絲毫不影響佈局。
原因是,專案裡總會存在著各種各樣的佈局方式,有frame佈局,有masonry autolayout,也有XIB拖線autolayout,我希望我寫的東西能讓使用的人很快的在自己專案裡接入,而不是一下刪掉舊的佈局方案,全都替換成我的。
我希望使用者直接在現有的程式碼裡,無論是哪種方式實現的介面,取到UIView,直接指定cssprotocol,就自動樣式生效了,不要讓使用者需要大規模改動現有程式碼。
同理,我也沒想讓這一套能夠動態的建立UI,真要動態建立原生UI,直接用samurai reactnative weex好了。
還原我的初衷,我還是希望原生開發者能在不改變自己的專案的情況下,很快的接入這個工具,對於主題樣式能夠控制的更靈活和方便。
題外話:
我就很不喜歡ASDK的設計,一整套非同步渲染,flexbox頁面佈局,網路,快取,滾動控制等等一堆完整解決方案雜糅在一起,讓使用者的代價異常的高,哪怕提供了UIKit轉ASNode的簡單入口也無法改變這一笨重無比的事實
其實ASDK每一個feature,單看原始碼,單獨拆出來模組,學習思想,吸收進入自己的專案都是很好地。
VKCssProtocol
整個專案的程式碼,以及使用demo,都在上面
這其實是一個為native開發準備的工具,是OC的程式碼,OC的實現,別被CSS的名字欺騙了╮(╯_╰)╭
對於這樣的iOS客戶端開發的場景,多少會有一定的幫助
- UI出App設計圖有一套標準UI庫,包括大中小標題,大中小按鈕,bar配色,分割線等
- 每種標準樣式都含有不同的底色,字號,字色,圓角,字型,陰影等等樣式屬性,屬性之間相互自有組合
- UI有時候更換整體設計風格的時候,所有專案中用到的標準元件,應該隨著UI設計一起動態生效為最新的設計效果
- 客戶端主題風格切換,換膚等系統
基本用法
簡單的看一個GIF吧,左邊就是CSS程式碼,後續我會給出目前已支援的CSS列表,在這裡寫完後,右側可以實時看到css效果,可以看到我準備了2個view樣式,準備了2個文字樣式,然後四個UI進行排列組合,任意交叉組合,實現各種靈活的設計
先在專案裡建立.css檔案
然後在裡面寫Css程式碼,這裡我粘個樣例
.commenView1{
background-color:orange;
border-top: 3px solid #9AFF02;
border-left: 5px solid black;
}
.commenView2{
background-color:#FF9D6F;
border-color:black;
border-width:2px;
border-radius:15px;
}
.commenText1{
color:white ;
font-size: 20px ;
text-align : right;
text-transform: lowercase;
text-decoration: line-through;
}
.commenText2{
color:black ;
font-size: 15px ;
text-align : right;
text-transform: uppercase;
text-decoration: underline;
}
複製程式碼
在iOS專案程式碼里載入Css
在didFinishLaunch or 某個你打算載入整體Css檔案的位置
//先import 標頭檔案
#import "VKCssProtocol.h"
//讀取bundle中名為cssDemo的css檔案
@loadBundleCss(@"cssDemo");
複製程式碼
對任意UI指定協議
UILabel *btabc = [[UILabel alloc]initWithFrame:CGRectMake(20, 50, self.view.bounds.size.width - 40, 80)];
btabc.text = @"commenView1 commenText1";
[self.view addSubview:btabc];
UILabel *lbabc = [[UILabel alloc]initWithFrame:CGRectMake(20, 150, self.view.bounds.size.width - 40, 80)];
lbabc.text = @"commenView2 commenText1";
[self.view addSubview:lbabc];
UILabel *btabcd = [[UILabel alloc]initWithFrame:CGRectMake(20, 250, self.view.bounds.size.width - 40, 80)];
btabcd.text = @"commenView1 commenText2";
[self.view addSubview:btabcd];
UILabel *lbabcd = [[UILabel alloc]initWithFrame:CGRectMake(20 , 350, self.view.bounds.size.width - 40, 80)];
lbabcd.text = @"commenView2 commenText2";
[self.view addSubview:lbabcd];
複製程式碼
上面的UI建立可以用任意方法建立,frame,autolayout,xib,隨便建立
只需要對指定的UI物件,賦值cssClass屬性,就可以指定css協議,就直接生效了,
btabc.cssClass = @"commenView1 commenText1";
lbabc.cssClass = @"commenView2 commenText1";
btabcd.cssClass = @"commenView1 commenText2";
lbabcd.cssClass = @"commenView2 commenText2";
複製程式碼
可以對一個UI物件,指定多個cssClass協議,他們一起組合生效,優先順序按最後生效的算
載入CSS的API
載入css主要依賴的是VKCssClassManager
這個類,但提供了4個巨集,可以快速方便的載入css
VKLoadBundleCss(@"cssDemo");
載入bundle內檔名為cssDemo的.css檔案
VKLoadPathCss(@"xxx/xxx.css");
載入路徑path下的css檔案
@loadBundleCss(@"cssDemo");
等同於VKLoadBundleCss,模擬了@語法糖
@loadPathCss(@"xxx/xxx.css");
等同於VKLoadPathCss,模擬了@語法糖
吐槽:
模擬@selector()這種的OC語法糖的方案真TM坑爹
凡是這種@loadBundleCss的巨集,是無法獲得xcode提供的程式碼自動補全的
直接使用VKLoadBundleCss,是可以獲得xcode程式碼自動補全的
跟RAC的@strongify @weakify一樣,無法獲得程式碼自動補全
這真的是一種只有裝B,沒球用的,看起來很pro的寫法
指定cssClass
上面貼過程式碼,我對所有的UIView都擴寫了一個category,裡面新增了一個屬性cssClass
,對這個屬性賦值,就相當於給這個UIView物件指定所遵從的cssClass協議,可以同時指定多個cssClass協議,用空格分開。
一個cssClass其實是一系列樣式屬性style的集合,將這一系列樣式屬性組合在一起,起個名字就是cssClass了,樣給一個UI指定了cssClass就相當於一組style都生效了。
btabc.cssClass = @"commenView1 commenText1";
lbabc.cssClass = @"commenView2 commenText1";
btabcd.cssClass = @"commenView1 commenText2";
lbabcd.cssClass = @"commenView2 commenText2";
複製程式碼
指定cssStyle
如果使用者並不打算專門寫一個cssClass,只是打算簡單的使用這個工具給一個ui賦值一個或幾個style,這也是支援的(嗯,常規的html元件也是可以寫class屬性和style屬性的嘛)
btabc.cssStyle = @"background-color:black border-color:black";
複製程式碼
我擴寫的category裡,還新增了一個屬性cssStyle
,對這個屬性賦值,就相當於給這個UIView物件不建立一個cssClass,直接寫一個或多個style使之生效
相當於你把一個或多個style寫法,用空格分開,直接賦值給cssStyle即可
目前支援的style
-
background-color:orange;
View的背景色樣式,冒號後是顏色引數,可以直接輸入顏色英文or #ffffff這樣的十六進位制色值 -
color:#ffffff
如果含有文字,文字的顏色,冒號後是顏色引數,可以直接輸入顏色英文or #ffffff這樣的十六進位制色值 -
font-size: 20px ;
如果含有文字,文字的字型大小,冒號後面是字號引數 -
border-color:red
View的邊框顏色,等同於layer.borderColor,冒號後是顏色引數,可以直接輸入顏色英文or #ffffff這樣的十六進位制色值 -
border-width: 2px
View的邊框寬度,等同於layer.borderWidth,冒號後是寬度引數 -
text-align: center
如果含有文字,文字的左右居中對齊,等同於TextAlignment,引數可以輸入left center right justify -
border-radius: 2px
View的邊框圓角,等同於layer.cornerRadius,冒號後面是半徑引數 -
text: abcdefg
如果含有文字,文字的內容,後面引數是字串 -
font-family: fontname
如果含有文字,文字的字型,等同於UIFont fontWithName的name,也可以直接輸入systemFont,boldSystemFont,italicSystemFont三個快捷輸入 -
background-image: imagenamed
如果含有image,image的名字,等同於UIImage的imageNamed的name -
text-shadow: 2px
如果含有文字,文字的陰影寬度,後面是數字引數 -
text-transform:uppercase
如果含有文字,文字的變化,包含uppercase,lowercase,capitalize三個值,全小寫,全大寫,首字母大寫 -
text-decoration:underline
如果含有文字,文字加特殊處理,包含underline,line-through兩個值,下劃線,刪除線 -
border-top: 3px solid #9AFF02
對UIView進行上右下左的單獨邊線處理,這個值是上邊線,第一個引數是寬度,solid後面是顏色 -
border-right: 3px solid #9AFF02
對UIView進行上右下左的單獨邊線處理,這個值是右邊線,第一個引數是寬度,solid後面是顏色 -
border-bottom: 3px solid #9AFF02
對UIView進行上右下左的單獨邊線處理,這個值是下邊線,第一個引數是寬度,solid後面是顏色 -
border-left: 3px solid #9AFF02
對UIView進行上右下左的單獨邊線處理,這個值是左邊線,第一個引數是寬度,solid後面是顏色
支援靈活擴充套件
上面提到的每一個style都是一個模組化元件,如果希望擴充套件新的style,只需要遵循並且實現模組化協議即可輕鬆地在整個框架裡,加入全新的style模組
以background-color
這個style模組為例
隨便新建一個繼承自NSObject的類,讓這個類遵從<VKCssStyleProtocol>
協議
#import <Foundation/Foundation.h>
#import "VKCssStylePch.h"
@interface VKBackgroundcolorStyle : NSObject<VKCssStyleProtocol>
@end
複製程式碼
然後在.m檔案實現裡,先使用VK_REGISTE_ATTRIBUTE()
巨集向框架註冊,然後必須實現2個類方法協議
- +styleName: 實現這個協議決定於你寫css的時候冒號前的名字
- +setTarget: styleValue: 實現這個協議決定於你如何解讀css裡面冒號後面的引數,並且處理傳入的target,也就是目標UIView
@implementation VKBackgroundcolorStyle
VK_REGISTE_ATTRIBUTE()
+ (NSString *)styleName{
return @"background-color";
}
+ (void)setTarget:(id)target styleValue:(id)value{
UIColor *color = [value VKIdToColor];
if ([target isKindOfClass:[UIView class]]) {
[(UIView *)target setBackgroundColor:color];
}
}
@end
複製程式碼
動態更新樣式
VKCssClassManager
這個類負責管理所有的css樣式表,我們希望這個css檔案就好像配置表一樣,可以動態下發,這樣在未來發版之後,也能改變app的主題樣式,自然就需要一套重新整理機制
+ (void)readBundleCssFile:(NSString *)cssFile;
+ (void)readCssFilePath:(NSString *)cssFilePath;
+ (void)reloadCssFile;
+ (void)clearCssFile;
複製程式碼
上面是VKCssClassManager
的介面,由於bundle裡的css檔案是不可更新的,因此重新整理機制與readBundleCssFile沒啥關係,只有通過readCssFilePath路徑載入的重新整理機制才有意義
- reloadCssFile 的用處就是沿著原路徑重新載入css,使用場景是新的css覆蓋了舊CSS路徑不變,在reloadCssFile的時候會自動觸發clearCssFile;
- clearCssFile 的用處是讓cssClassManager清空目前所管理的所有class;
- 在不直接使用reloadCssFile的情況下,可以先執行clearCssFile,再執行readCssFilePath,從而實現清空css後載入新路徑的css檔案
HotReloader
大家在Gif裡看到了像playground一樣,無需編譯和重新執行,每改一行程式碼,介面就立刻實時生效的效果,主要是額外寫了一個外掛HotReloader
由於HotReloader的設計初衷是給除錯,高效的實時看效果用的,因此整個HotReloader通過編譯控制,所有函式只有在模擬器編譯的情況下才有效,真機下HotReloader回自動失效
這個HotReloader不是必須的,你完全可以不使用它,整個CssProtocol一樣可以work
想要使用它需要先import標頭檔案#import "VKCssHotReloader.h"
,然後在準備載入Css的地方用預編譯控制,控制模擬器下載入css的程式碼變為hotReloader監聽Css
#if TARGET_IPHONE_SIMULATOR
//playground除錯
//JS測試包的本地絕對路徑
NSString *rootPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"projectPath"];;
NSString *cssPath = [NSString stringWithFormat:@"%@%@", rootPath, @"/cssDemo.css"];
[VKCssHotReloader hotReloaderListenCssPath:cssPath];
#else
VKLoadBundleCss(@"cssDemo");
#endif
複製程式碼
這個絕對路徑一定要填Mac的磁碟檔案路徑喲,用過JSPatchPlaygroundTool的一定不會陌生
做完這件事之後還要注意2個事情
- 在你打算開啟除錯的地方呼叫
[VKCssHotReloader startHotReloader];
(比如某個介面的ViewDidLoad) - 在你打算停止除錯的地方呼叫
[VKCssHotReloader endHotReloader];
(比如某個介面的dealloc)
為什麼要這麼做,因為一旦當你startHotReloader的時候,所有進行過cssClass,cssStyle設定的view都會被建立一個監聽,因此會造成View物件的額外持有導致的不釋放,因此當你不打算HotReload了就要關閉這個監聽endHotReloader
因為這樣的設計有可能造成使用不當的記憶體Leak,所以對HotReloader的所有程式碼都進行了編譯控制,只有模擬器下才會工作,真機orRelease包下,無論你怎麼忘記寫endHotReloader都不會造成Leak
補充
整個結構大體如這樣,採用模組化的設計之後,就是有需求完全按著自己的意願任意擴充新支援的style屬性了。
不過有一點要補充的是
由於最近比較忙,這玩意都拖了半個月才湊合寫完,我目前已經支援的很多屬性,其實實現並不是很優雅
比如
- border-top
- border-bottom
- border-right
- border-left
- font-weight
四邊獨立邊線湊合用比較low的方法做了,只是圖快,以後這四個模組還得再好好雕琢一下
字型加重這個模組,用的stroke結果會把字型變鏤空,反正沒啥工夫好好細弄一下
後續我還打算做 四個角不同弧度的圓角屬性
總之,這玩意還會不斷完善補充