iOS上的CSS樣式協議 VKCssProtocol

Jorcl 8年前發布 | 10K 次閱讀 CSS iOS開發 移動開發

早先,寫過一陣子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:3pxsolid#9AFF02;
border-left:5pxsolid 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 = [[UILabelalloc]initWithFrame:CGRectMake(20,50,self.view.bounds.size.width -40,80)];
btabc.text = @"commenView1 commenText1";
[self.view addSubview:btabc];

UILabel*lbabc = [[UILabelalloc]initWithFrame:CGRectMake(20,150,self.view.bounds.size.width -40,80)];
lbabc.text = @"commenView2 commenText1";
[self.view addSubview:lbabc];

UILabel*btabcd = [[UILabelalloc]initWithFrame:CGRectMake(20,250,self.view.bounds.size.width -40,80)];
btabcd.text = @"commenView1 commenText2";
[self.view addSubview:btabcd];

UILabel*lbabcd = [[UILabelalloc]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"

@interfaceVKBackgroundcolorStyle:NSObject<VKCssStyleProtocol>

@end

然后在.m文件實現里,先使用 VK_REGISTE_ATTRIBUTE() 宏向框架注冊,然后必須實現2個類方法協議

  • +styleName: 實現這個協議決定于你寫css的時候冒號前的名字
  • +setTarget: styleValue: 實現這個協議決定于你如何解讀css里面冒號后面的參數,并且處理傳入的target,也就是目標UIView
@implementationVKBackgroundcolorStyle

VK_REGISTE_ATTRIBUTE()

+ (NSString*)styleName{
return@"background-color";
}

+ (void)setTarget:(id)target styleValue:(id)value{
UIColor*color = [value VKIdToColor];
if([target isKindOfClass:[UIViewclass]]) {
 [(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 = [[NSBundlemainBundle] objectForInfoDictionaryKey:@"projectPath"];;
NSString*cssPath = [NSStringstringWithFormat:@"%@%@", 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

 

來自:http://awhisper.github.io/2016/11/01/cssprotocol/

 

 本文由用戶 Jorcl 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!