做者 | 希德編輯 | 張之棟、王文婧在APP開發中,快速實現效果相當重要,而樣式的可複用、易維護能夠幫助開發人員作到這一點。本文做者開發的Swift-CSS正是這樣一個實現樣式系統的庫,它將CSS的技術優點引入SwiftUI開發中,不只能夠實現SwiftUI裏樣式屬性的複用、解構,還能變化出許多相似Web領域的優秀技術方案。經過了解它的應用方法,或許會對你的平常工做有所助益。本文的主角 SwiftUI-CSS 是我一個多月前實現的一個 SwiftUI 庫,它的目的是實如今 Web 開發領域結構樣式分離的效果:css
CSS 負責結構樣式。html
樣式不寫在 HTML 的屬性裏,而是在 CSS 當中,不只僅是爲了解耦,更重要的是複用,促使開發者把全部的業務樣式需求分解,提煉良好的基礎樣式,以更系統的方式管理樣式。前端
CSS 自然地提供 classname 機制,能夠實現樣式分組和組合。一個業務樣式的最終效果能夠由一些基礎樣式組合而成,不一樣組合呈現不一樣的效果。git
<div class="fontStyle colorStyle floatStyle">
</div>程序員
本質上講, CSS 裏的一個 classname 封裝了一組屬性(property)的集合,簡稱樣式。多個 classname 便可組合成爲一個樣式系統,一個樣式系統實現業務上的組件設計,配合具體的 HTML 結構就是一個組件(component)。github
SwiftUI-CSS 將 CSS 的技術優點引入 SwiftUI 開發中,不只能夠實現 SwiftUI 裏樣式屬性的複用、解構,還能夠變化出不少相似 Web 領域的優秀技術方案。SwiftUI-CSS 的詳細使用可參見 SwiftUI-CSS readme: https://github.com/hite/SwiftUI-CSS/blob/master/README.md編程
本文試圖探討 SwiftUI-CSS 爲 SwiftUI,乃至 iOS 開發帶來的促進做用與影響。bootstrap
(閱讀本文須要你對 SwiftUI 有基本的瞭解)swift
什麼是樣式系統?樣式系統指的是在 UI 設計規範中,提煉出來的一些規範。以 Ant Design 爲例。它的「字體使用規範」裏指出,主標題的樣式是這樣的:行高行間距(當文字有可能多行時)line-height。前端工程化
main_title
做爲樣式系統裏的命名):
.main_title{
color: rgb(102, 102, 102);
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", 微軟雅黑, SimSun, sans-serif;
font-size: 16px;
font-weight: 500;
}
定義好以後,.main_title
就表明了設計師對 主標題 的視覺要求,能夠在後面的界面中反覆使用,而不須要字體、字號、字重、顏色再定義一遍。
這是比較基礎的樣式,稍微複雜點的例子是按鈕,按鈕的樣式不只包含字體的樣式,還包括按鈕的邊距、圓角、背景色等屬性。
.buttonStyle
裏。
.buttonStyle{
width: 212px;
height: 40px;
border-radius: 20px;
background-color: #dd1a21;
line-height: 40px;
font-family: PingFang-SC-Bold;
font-size: 16px;
color: #FFFFFF;
text-align: center;
}
後續界面裏須要這種肯定按鈕的時候,只須要引用 .buttonStyle
樣式名就能夠了。更好的例子可參考 Twitter 出品的 Bootstrap 來學習如何組織管理 CSS 樣式: https://getbootstrap.com/
使用樣式系統,要求視覺和開發同窗對總體視覺有全局掌握。對於視覺同窗,梳理視覺規範,定義哪些是通用規則,哪些是個性規則,哪些是基礎規則,以及如何對基礎規則進行運算;開發同窗提供樣式接口時,須要在實現視覺要求的基礎上,還可以保證擴展性和易讀性。在對視覺規範有深刻理解以後,設計出來的視覺規範纔有用,更健壯。
可複用。若是視覺稿是按照原有規範實現的,那麼新需求裏的頁面,也可使用已有的樣式來快速搭建,就像搭積木同樣。
main_title
,
buttonStyle
是基礎元素樣式。在組件庫裏,會有一些基礎元素樣式、基礎功能樣式。一些複雜的組件須要用這些基礎元素樣式、基礎功能樣式組合。
/** 元素樣式 **/
.w-seperator{
height: 2px;
width: 100%;
backgroundColor: #ff00ff;
}
/** 功能樣式 **/
.f-hide{
display:none;
}
/** 功能樣式 **/
.f-clear_both{
clear:both;
}
// 請忽略這個樣式的實際意義
<div class="w-seperator f-clear_both f-hide"></div>
這裏w-seperator f-clear_both f-hide
便是這個分割線的樣式名稱。
w-seperator f-clear_both f-hide
並非那麼簡潔。如藉助預編譯,還可使用變量、繼承等特性來簡化 CSS 的定義工做。比方使用 sass: https://sass-lang.com/
.w-seperator{
height: 2px;
width: 100%;
backgroundColor: #ff00ff;
}
.f-hide{
display:none;
}
.f-clear_both{
clear:both;
}
.seperator_in_list{
@extend .w-seperator;
@extend .f-hide;
@extend .f-clear_both;
}
這樣.seperator_in_list
這個名字就是咱們在後面界面裏可用的樣式名,比起 CSS 是否是更見文知意,更易用呢?
// 黑色中空,中間是 clear color
+ (instancetype)yx_BlackHollowClearButton {
YXButton* button = [YXButton new];
button.titleLabel.font = [UIFont systemOfSize:14];
[button setTitleColor:YXColorGray4 forState:UIControlStateNormal];
[button setTitleColor:YXColorWhite forState:UIControlStateHighlighted];
[button setTitleColor:YXColorGray10 forState:UIControlStateDisabled];
button.layer.borderWidth = YX_ONE_PIXEL;
button.layer.borderColor = YXColorGray4;
button.layer.cornerRadius = YXButtonCornerRadius;
button.layer.masksToBounds = YES;
return button;
}
UILabel *label = [UILabel new];簡單的對照,發現複用只能複用屬性,如舉例中的
[NYQSpec setLabelStyle:label withNYQCode:NYQCode_18_blk_med];
label.textAlignment = NSTextAlignmentCenter;
label.text = @"請確認如下信息";
YXColorGray4
和
NYQCode_18_blk_ med
,若是要設置一組屬性,須要再次設置,沒有一個對象如
importantStyle
來表明顏色和字體等,使得下一個 button 能夠直接設置
importantStyle
的。
// 不存在這樣的系統接口
UIStyle *importantStyle = [UIStyle styleWithColor: [UIColor redColor] font: YX_Button_Font];
// 確認按鈕
UIButton *confirm = [UIButton new];
[confirm setStyle: importantStyle];
// 提示按鈕
UIButton *prompt = [UIButton new];
[prompt setStyle: importantStyle];
我想緣由就是 Cocoa touch 設計之初就沒有考慮用對象來表示一組屬性,沒有設計樣式系統的概念,致使在封裝實現樣式系統時比較困難。
補充提示[button setStyle:]
這個接口其實可使用
category
技術來實現,
UIStyle
能夠用自定義封裝,只要 UIStyle 實現了接口,任何樣式的屬性均可以封裝到一個 UIStyle 的實例中。這種方式和下面即將介紹的 SwiftUI-CSS 的封裝本質的不一樣在於,UIStyle 裏的屬性不能運算,
[button setStyle:]
本質是把屬性掛在一個全局變量下,而後遍歷,在性能方面沒有提高,充其量是一種語法糖。
UIStyle *importantStyle = [UIStyle styleWithColor: [UIColor redColor] font: YX_Button_Font];
// 一種 setStyle 內部實現
- (void)setStyle:(UIStyle *)style{
if(style.font){
self.titleLabel.font = style.font
}
if(style.color){
[self setTitleColor:style.color forState:UIControlStateNormal];
}
}
// 理想中的,目前沒法實現
- (void)setStyle:(UIStyle *)style{
if (style.computedStyle == nil) {
[style compute];
}
// computedStyle 包含了字體和顏色
[self setFinalStyle:style.computedStyle];
}
理想中的 computedStyle 真正使用到樣式上,纔對全部屬性進行一次計算,這樣在後續其餘 button 設置時,直接使用計算結果,而不是再次使用遍歷的方式去一一設置。屬性計算帶來的性能提高,相似在 JS 模板引擎中經常使用的字符串模板編譯成 function 帶來的效果,甚至更高。
使用 storyboard 的界面開發使用代碼實現樣式系統,至少還可使用所有變量、宏、函數封裝來達到某種意義上的複用、維護。可是若是使用 storyboard 實現的界面,則須要面對更多的問題。storyboard 在快速搭建單個界面時效率很是高。假設須要更新品牌色時,至少還能夠用 asset catalog
來實現全局的顏色修改,可是涉及到如「主標題」字號修改時,則顯得無能爲力,只能一個一個 storyboard 去修改,更不要說一塊兒修改多個屬性的組合了。
storyboard 最多能夠在小組件層複用,向上到 ViewController 粒度太多,不容易複用;向下只能使用 xib 複用組件—— storyboard 不存在樣式系統。
直到 SwiftUI 橫空出世,把描述性界面開發體驗帶到 iOS,它的函數式語法和屬性對象方式,使得人們能夠用 Swift-CSS 來實現 SwiftUI 裏的樣式系統。
SwiftUI SwiftUI 裏的鏈式語法,是函數式函數調用的體現。SwiftUI 實體分爲View
和
ContentModifier
。
Text("g_kumar")
負責視圖結構;
.font(.title)
添加屬性樣式。簡單的實例以下:
Text("g_kumar")
.bold()
.font(.title)
Text("Notifications: \(self.profile.prefersNotifications ? "On": "Off" )")
Text("Seasonal Photos: \(self.profile.seasonalPhoto.rawValue)")
Text("Goal Date: \(self.profile.goalDate, formatter: Self.goalFormat)")
g_kumar
文字組件爲例,咱們應用函數式編程裏的運算規律 - 結合律推導一番:
v
表示。cm1
表示。cm2
表示。C
。C = v * cm1 * cm2
// =>
C = (v * cm1) * cm2
// =>
C = v *( cm1 * cm2)
// =>
cm = cm1 * cm2
C = v * cm
// 假設 v1 是另一個 Text,則
C1 = v1 * cm
因此,上面公式裏的 cm
表明了樣式的計算結果,在這裏是指字形和字號的運算結果。利用這個計算結果,在後面的樣式設置 v1
,v2
等視圖時,能夠直接使用 cm
來設置樣式。它帶來的性能提高,取決於 Apple 對 cm
這個計算變量的內部優化程度。鑑於目前 SwiftUI 閉源,咱們還沒法得知這種優化帶來多大的提高。退一步講,將計算結果封裝爲一個變量,當 Apple 後續對 ContentModifier 計算進行優化後,調用者可透明地享受到優化提高。
以上就是屬性運算的原理,因此有了 SwiftUI-CSS。
SwiftUI-CSS 的樣式系統SwiftUI 的原理很簡單,就是使用CSSStyle
對象來封裝樣式對象,而後經過
addClassName
這個 modifier 將樣式插入函數運算中,和其餘事件、通知、樣式(.frame\ .resizable)一塊兒無縫協做。以 SwiftUI-CSS example 工程爲例:
// without SwiftUI-CSS
Image("image-swift")
.resizable()
.scaledToFit()
.frame(width:100, height:100)
.cornerRadius(10)
.padding(EdgeInsets(top: 10, leading: 0, bottom: 15, trailing: 0))
// with SwiftUI-CSS
let languageLogo_clsName = CSSStyle([
.width(100),
.height(100),
.cornerRadius(10),
.paddingTLBT(10, 0, 15,0)
])
Image("image-swift")
.resizable()
.scaledToFit()
.addClassName(languageLogo_clsName)
其中,languageLogo_clsName
就是 logo 的樣式名,在頁面其餘 logo,能夠直接複用這個樣式。更多使用示例請查看 SwiftUI-CSS example 工程: https://github.com/hite/SwiftUI-CSS_example
-- ProductDetail |----ProductDetailView.swift |----ProductDetailStyle.swift
ProductDetailView.swift 負責構建界面的結構,裏面只有 view 元素、事件邏輯、數據流等,保持簡潔。而 ProductDetailStyle.swift 裏面是一些樣式的定義。兩個文件分離有助於 diff 、review 及與他人協做。
複用
當有視覺規範後,按照規範,在公用的樣式文件裏,預先定義好全部基礎樣式,如「主標題」文字樣式等,而後定義若干公用的業務樣式,如出錯彈窗。理想狀況下,業務樣式和組件樣式均可以像搭積木同樣,由這些基礎樣式拼湊而成。
性能提高
按照理論,CSSStyle 這樣的計算結果,是一種相似編譯後的緩存(compiled code),老是有提高的。具體的測試數據,待 iPhone 11 上市和 macOS 10.15 發佈以後再作評測。請關注 SwiftUI-CSS,後續會補充。
樣式繼承
CSS 裏的樣式系統
的例子),SwiftUI-CSS 也內置了:
let fontStyle = CSSStyle([.font(.caption)])
let colorStyle = CSSStyle([.backgroundColor(.red)])
let finalStyle = fontStyle + colorStyle
button.addClassName(finalStyle)
利用CSSStyle
提供的+
運算,將多個樣式合併實現繼承效果。
以上只是我我的實踐中遇到的場景,在別人的手裏可能還會迸發出不同的火花,如下是個人一些構想:
SwiftUI zen garden 計劃在 Web 開發早期,人們對 CSS 在 Web 開發中扮演的角色定位不是很清晰。2003 年,Dave Shea 發起了 CSS zen garden 計劃。這個網站提供一套固定的帶樣式名,可是沒有樣式實現的 .html 文件,而後參與者提供不一樣的 CSS 文件,對相同的 HTML 結構進行 stylize
,試圖探索 CSS 對 HTML 結構可定製能力的極限。時至今日,已經有 218 個五花八門的設計位列 Design List 其中,不少充滿想象力的設計讓人歎爲觀止。
參與者提供對這些樣式名的實現文件,如 style.swift,和 html.swift 一塊兒生成不一樣的界面設計。讓咱們一塊兒探索使用 SwiftUI 可定製能力的極限。
以上方案被稱爲 SwiftUI zen garden(待實施)。
設計師和程序員協做——storyboard 的夙願xib(storyboard 前身)早在 iOS1.0 以前就被 Apple 用在 iOS 的開發工做流中:設計師用 Interface Builder 編寫 xib 文件,以後程序員用 xcode,在 xib 的基礎上繼續編寫事件、數據等業務邏輯。可是由於 xib 變動後較難,diff 和 xib 並非程序員使用的 oc 語言,不能無縫複用,致使設計師和程序員分離開發的目的沒有實現。
大部分設計師用 xib 完成的 App prototype,都不能直接讓程序員繼續開發。
更多時候 xib 的工程只是爲了作 App 原型,程序員還須要按照 prototype,徹底或者部分用代碼重寫。
有了 SwiftUI,設計師可使用 SwiftUI 編寫 prototype,驗證完畢以後,程序員拿 SwiftUI 源碼繼續開發,由於都是 swift 文件啊。設計師後續的樣式調整,能夠直接修改 style.swift 文件,不須要和程序員去競爭 html.swift 文件使用權,避免衝突。設計師和程序員無縫協做的大和諧,在 SwiftUI 中得以實現!
也許你還能想到更多用法,是否是?
後記SwiftUI-CSS 1 個月前就寫好了,當我發佈到 Twitter、Hacknews 等地方,邀請各位大 V 宣傳時,並無激起多少浪花,我認爲它的重要性被低估了,故做此文。
參考連接: https://sass-lang.com https://en.wikipedia.org/wiki/CSS_Zen_Garden
活動推薦前端工程化是前端業務以及技術架構複雜度提高的必行之路,但工程化面臨的挑戰始終很大。在這個超級 APP 割據,又有大量後繼挑戰者的移動互聯網後半場,沒有最好的工程化方案,只有最適合的工程化方案。
QCon 日程已上線,點擊「閱讀原文」或識別二維碼來 QCon 上海 2019 看各個互聯網公司一系列經歷實戰考驗的前端實踐。大會 報名倒計時,團購享優惠,有任何問題歡迎聯繫票務小姐姐 Ring:17310043226(微信同號)