我開發了一個SwiftUI庫,將CSS引入iOS開發

我開發了一個SwiftUI庫,將CSS引入iOS開發


image.png

做者 | 希德編輯 | 張之棟、王文婧在APP開發中,快速實現效果相當重要,而樣式的可複用、易維護能夠幫助開發人員作到這一點。本文做者開發的Swift-CSS正是這樣一個實現樣式系統的庫,它將CSS的技術優點引入SwiftUI開發中,不只能夠實現SwiftUI裏樣式屬性的複用、解構,還能變化出許多相似Web領域的優秀技術方案。經過了解它的應用方法,或許會對你的平常工做有所助益。本文的主角 SwiftUI-CSS 是我一個多月前實現的一個 SwiftUI 庫,它的目的是實如今 Web 開發領域結構樣式分離的效果:css

  • HTML 負責結構。
  • 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 爲例。它的「字體使用規範」裏指出,主標題的樣式是這樣的: image.png

主標題的樣式至少包含 5 個關鍵屬性:
  1. 字體 font family(包括英文字體)。
  2. 字重 font weight。
  3. 字號 font size。
  4. 字體顏色 color。
  5. 行高行間距(當文字有可能多行時)line-height。前端工程化

若是用 CSS ,那麼它的樣式定義是這樣的(以 main_title 做爲樣式系統裏的命名):
.main_title{
    colorrgb(102, 102, 102);
    font-family"Helvetica Neue", Helvetica, "PingFang SC""Hiragino Sans GB""Microsoft YaHei", 微軟雅黑, SimSun, sans-serif;
    font-size16px;
    font-weight500;
}

定義好以後,.main_title 就表明了設計師對 主標題 的視覺要求,能夠在後面的界面中反覆使用,而不須要字體、字號、字重、顏色再定義一遍。

這是比較基礎的樣式,稍微複雜點的例子是按鈕,按鈕的樣式不只包含字體的樣式,還包括按鈕的邊距、圓角、背景色等屬性。image.pngimage.png

看起來須要定義的樣式還有點多,但得益於 CSS 的層疊樣式的特性,文字和按鈕兩部分的代碼能夠寫到一個樣式 .buttonStyle裏。
.buttonStyle{
            width212px;
            height40px;
            border-radius20px;
            background-color#dd1a21;
            line-height40px;
            font-family: PingFang-SC-Bold;
            font-size16px;
            color#FFFFFF;
            text-align: center;
        }

後續界面裏須要這種肯定按鈕的時候,只須要引用 .buttonStyle 樣式名就能夠了。更好的例子可參考 Twitter 出品的 Bootstrap 來學習如何組織管理 CSS 樣式: https://getbootstrap.com/ 

爲何樣式系統對 App 開發很重要
  1. 使用樣式系統,要求視覺和開發同窗對總體視覺有全局掌握。對於視覺同窗,梳理視覺規範,定義哪些是通用規則,哪些是個性規則,哪些是基礎規則,以及如何對基礎規則進行運算;開發同窗提供樣式接口時,須要在實現視覺要求的基礎上,還可以保證擴展性和易讀性。在對視覺規範有深刻理解以後,設計出來的視覺規範纔有用,更健壯。

  2. 做爲頁面仔,在平常工做中,快速實現效果是很是重要的,但願咱們的樣式:
  • 可複用。若是視覺稿是按照原有規範實現的,那麼新需求裏的頁面,也可使用已有的樣式來快速搭建,就像搭積木同樣。

  • 易維護。並且實際工做中,在某個具體頁面迭代最多的恐怕就是視覺優化了。若是你使用的樣式系統,在處理二行變三行、按鈕右上角加個圖標、整個文字描述塊總體向右移動等等需求變化時,若是可以快速實現,而不是須要結構大改(這樣容易改出新問題),那麼說明你的樣式系統和 UI 接口劃分是面向需求變化的。可以應付大部分(不要求 100 %)需求增改,就是個設計良好的組件。
CSS 裏的樣式系統上述的 main_title, buttonStyle 是基礎元素樣式。在組件庫裏,會有一些基礎元素樣式、基礎功能樣式。一些複雜的組件須要用這些基礎元素樣式、基礎功能樣式組合。
 
 

/** 元素樣式 **/
.w-seperator{
    height2px;
    width100%;
    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 便是這個分割線的樣式名稱。

這是原生 CSS 就支持的使用方式,仍是比較粗放。 w-seperator f-clear_both f-hide 並非那麼簡潔。如藉助預編譯,還可使用變量、繼承等特性來簡化 CSS 的定義工做。比方使用 sass: https://sass-lang.com/ 
 
 

.w-seperator{
    height2px;
    width100%;
    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 是否是更見文知意,更易用呢?

iOS 開發中的樣式系統Cocoa touch 並無提供樣式系統的語法,有些開發者可能會本身封裝一層,大部分封裝都比較初級。比方說只對 App 裏的按鈕封裝了工廠類,或者只對 Label 設置字號、字體、顏色作了封裝,沒有造成進一步封裝。
  • 對按鈕 Button 的封裝
 
 

// 黑色中空,中間是 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 的樣式封裝
UILabel *label = [UILabel new];
    [NYQSpec setLabelStyle:label withNYQCode:NYQCode_18_blk_med];
    label.textAlignment = NSTextAlignmentCenter;
    label.text = @"請確認如下信息";
簡單的對照,發現複用只能複用屬性,如舉例中的 YXColorGray4NYQCode_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 實體分爲 ViewContentModifierText("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)")

image.pngg_kumar文字組件爲例,咱們應用函數式編程裏的運算規律 - 結合律推導一番:
  • Text("g_kumar") 用 v 表示。
  • .bold() 用 cm1 表示。
  • .font(.title)  用 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(10015,0)
])
Image("image-swift")
        .resizable()
        .scaledToFit()
        .addClassName(languageLogo_clsName)

其中,languageLogo_clsName就是 logo 的樣式名,在頁面其餘 logo,能夠直接複用這個樣式。更多使用示例請查看 SwiftUI-CSS example 工程: https://github.com/hite/SwiftUI-CSS_example  

總結下 SwiftUI-CSS 帶來的好處:  解耦 如同 Web 領域開發那樣,.html 、.css 文件是分開的。 以產品詳情爲例,典型的目錄結構是:

-- ProductDetail  |----ProductDetailView.swift  |----ProductDetailStyle.swift


ProductDetailView.swift 負責構建界面的結構,裏面只有 view 元素、事件邏輯、數據流等,保持簡潔。而 ProductDetailStyle.swift 裏面是一些樣式的定義。兩個文件分離有助於 diff 、review 及與他人協做。


 複用


當有視覺規範後,按照規範,在公用的樣式文件裏,預先定義好全部基礎樣式,如「主標題」文字樣式等,而後定義若干公用的業務樣式,如出錯彈窗。理想狀況下,業務樣式和組件樣式均可以像搭積木同樣,由這些基礎樣式拼湊而成。


 性能提高


按照理論,CSSStyle 這樣的計算結果,是一種相似編譯後的緩存(compiled code),老是有提高的。具體的測試數據,待 iPhone 11 上市和 macOS 10.15 發佈以後再作評測。請關注 SwiftUI-CSS,後續會補充。


 樣式繼承


在 CSS 領域,sass 提供的一些高級應用,如樣式繼承(見第三節 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 其中,不少充滿想象力的設計讓人歎爲觀止。

CSS zen garden 的成功,讓開發者意識到 CSS 的無限可能性,同時也激勵諸多其它語言嘗試相同的項目,也一樣影響到我,而 SwiftUI-CSS 提供了可能性:
  • 提供一套固定的編寫了 View 結構的文件,如 html.swift,帶樣式名可是沒有設置屬性。
  • 參與者提供對這些樣式名的實現文件,如 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(微信同號)

相關文章
相關標籤/搜索