前言
本文要講述的是如何使用 YogaKit
提供的 FlexBox 布局能力來(lái)實(shí)現(xiàn)類似 Xib 的功能(Xib 布局是 AutoLayout)。
GitHub 地址:https://github.com/danleechina/YogaLayout
構(gòu)想
Xib(StoryBoard) 的原理
先說(shuō)一下 Xib(StoryBoard) 的原理:
- 在 Xcode 中使用 Interface Builder 來(lái)構(gòu)建一個(gè)界面或者視圖榛鼎。這個(gè)過(guò)程其實(shí)就是創(chuàng)建枷踏、修改 XML 界面描述文件。右鍵 xib 類型的文件,在 Open As 中選擇 Source Code目锭,可以看到 xib 類型的文件其實(shí)就是蘋(píng)果用自己定義的一套 XML 來(lái)描述視圖
- 編譯的時(shí)候厌秒,IDE 會(huì)解析項(xiàng)目中所有的 xib 類型的文件,根據(jù)蘋(píng)果自己的一套規(guī)則(我們不可知)汉形,生成一個(gè)可歸檔的類纸镊,然后將該類歸檔生成一個(gè) nib 類型的二機(jī)制文件。如果項(xiàng)目中有使用 xib 的話概疆,解壓打包出來(lái)的 ipa逗威,就會(huì)看到這些 nib 類型的文件,這些文件就是 IDE 在編譯時(shí)候解析生成的岔冀。
- 運(yùn)行時(shí)凯旭,當(dāng)需要展示一個(gè) xib 描述的視圖時(shí),iOS 系統(tǒng)則會(huì)去加載 nib 文件得到之前編譯時(shí)候生成的歸檔類實(shí)例使套,根據(jù)這個(gè)實(shí)例生成視圖需要的所有子視圖罐呼,并設(shè)置這些子視圖的約束和屬性
- 最后如果有設(shè)置 IBOutlet 的話,會(huì)將生成的視圖賦值給其 owner侦高。
想要了解 xib 更加深入的內(nèi)容可以查看我之前翻譯的一篇文章:macOS 和 iOS 中 Nib 文件實(shí)現(xiàn)原理以及最佳實(shí)踐
可以看到嫉柴,要實(shí)現(xiàn)類似 Xib 這樣的工具需要如下一些條件:
- 一套類似的 XML 來(lái)描述視圖的組成(比如某個(gè)視圖有哪些子視圖,它又是那個(gè)視圖的子視圖)奉呛,配置視圖的屬性(比如配置 label 的 text 屬性)计螺,以及定義視圖之間的布局(比如使用 autolayout)
- 出于性能考慮,可以提前解析該 XML瞧壮,生成類似 nib 的類
- 運(yùn)行時(shí)解析該 XML(如果在編譯階段提前解析了登馒,則不需要),將 XML 中視圖的組成咆槽,視圖的屬性陈轿,視圖的布局給提取出來(lái),動(dòng)態(tài)生成相應(yīng)視圖,并配置其屬性济欢、子視圖赠堵、父視圖、布局等
- 如果 XML 描述的文件有指定的 Outlet 的話法褥,則將其賦值給相應(yīng)的類
構(gòu)想實(shí)現(xiàn)
定義 XML 描述視圖規(guī)則
<UIView id="gradeTipView" style="alignItems:center;justifyContent:center; marginLeft:12;marginRight:8">
<UILabel id="gradeNameLabel" style=""></UILabel>
<StarBarView id="starBarView" style=""></StarBarView>
</UIView>
- 在這里茫叭,每一個(gè) XML 標(biāo)簽名字是視圖類的名字,以便運(yùn)行時(shí)通過(guò)
NSClassFromString
得到類對(duì)象半等,再通過(guò)類對(duì)象生成該視圖類的一個(gè)實(shí)例 - 每一個(gè) XML 標(biāo)簽有一些屬性揍愁,比如上面的 id 和 style。id 的作用等同于 IBOutlet杀饵,將動(dòng)態(tài)生成的類實(shí)例賦值給其 owner莽囤,style 的作用是描述布局,然后將其設(shè)置到 YogaKit 中切距。還可以添加別的屬性朽缎,比如說(shuō)如果是 UILabel 的話設(shè)置
text="我是文本"
來(lái)描述其 text 屬性,代碼中使用 KVC 來(lái)實(shí)現(xiàn) - style 屬性的值指定多個(gè)的時(shí)候需要通過(guò)分號(hào)隔離谜悟,鍵值對(duì)通過(guò)冒號(hào)隔離
- 每一個(gè) XML 標(biāo)簽會(huì)包含其他 XML 標(biāo)簽话肖,比如上面 UIView 包含一個(gè) UILabel 標(biāo)簽,一個(gè)自定義的 StarBarView 標(biāo)簽葡幸,意思就是說(shuō)該 UIView 有兩個(gè)子視圖最筒,第一個(gè)是一個(gè) UILabel,第二個(gè)是自定義視圖 StarBarView
- 為了簡(jiǎn)單蔚叨,每一個(gè)類似該 XML 的文件只能有一個(gè)根標(biāo)簽
比如不能出現(xiàn)下列場(chǎng)景:
<UIView></UIView>
<UILabel></UILabel>
<UIButton></UIButton>
在這里床蜘,根標(biāo)簽有三個(gè),第一個(gè) UIView蔑水,第二個(gè) UILabel邢锯,第三個(gè) UIButton,這個(gè)情況目前不被允許(對(duì) xib 了解的可能會(huì)知道從 nib 獲取視圖對(duì)象會(huì)返回一個(gè)數(shù)組搀别,就是說(shuō) xib 可以有多個(gè)根層級(jí)的視圖)
實(shí)現(xiàn)細(xì)節(jié)
- 如何解析 XML丹擎?為了簡(jiǎn)單起見(jiàn)我直接使用了蘋(píng)果官方處理 XML 的類
NSXMLParser
。實(shí)現(xiàn)代理方法领曼,調(diào)用parse
函數(shù)即可完成 XML 解析。有一點(diǎn)要注意蛮穿,調(diào)用parse
函數(shù)是同步的庶骄,也就是說(shuō)當(dāng)parse
函數(shù)返回時(shí),如果實(shí)現(xiàn)了代理方法践磅,則代理方法均已經(jīng)被使用過(guò)了单刁。 - 解析 XML 的結(jié)果是一個(gè)樹(shù)形的數(shù)據(jù)模型,該樹(shù)形結(jié)構(gòu)的每一個(gè)節(jié)點(diǎn)包含視圖類的名字,視圖的屬性(包括視圖的布局信息)羔飞,子視圖信息肺樟,以及父視圖信息(沒(méi)有父視圖意味則是根節(jié)點(diǎn))
- 從樹(shù)形數(shù)據(jù)模型的根節(jié)點(diǎn)開(kāi)始動(dòng)態(tài)生成該視圖的實(shí)例,并配置實(shí)例的屬性逻淌,子視圖信息和父視圖信息等
- 在 XML 中有時(shí)候定義一個(gè)視圖的寬高時(shí)會(huì)需要屏幕寬高的信息么伯,并做一下簡(jiǎn)單表達(dá)式的計(jì)算,比如視圖的高時(shí)屏幕的高的 2/3卡儒,這里做表達(dá)式計(jì)算的時(shí)候可以使用
NSExpression
來(lái)處理田柔。 - 用 Swift 編寫(xiě)的視圖類使用 Objective C 來(lái)動(dòng)態(tài)生成的時(shí)候,需要加上當(dāng)前二機(jī)制文件的名字骨望,這個(gè)需要注意
未來(lái)
- 支持動(dòng)態(tài)設(shè)置文本硬爆、顏色、圖片擎鸠、CGRect(結(jié)構(gòu)體) 等屬性
- 簡(jiǎn)化 Swift 中 Outlet 的設(shè)置
- 考慮能否去掉 Outlet缀磕,使用者傳入數(shù)據(jù),數(shù)據(jù)和視圖能夠雙向綁定
- 支持 Debug 模式下不重新編譯劣光,僅需更改 XML 文件即可更新視圖布局