近期開始進行一個新項目的原型制作及其結(jié)構(gòu)設(shè)計拘鞋,打算把一些心路歷程記錄下來,隨便先給它取個名字叫做:P-OOP灰蛙。
比起手寫 UI傅事,“拖控件”的 Storyboard 和 Xib 似乎一直都更投我所好。不過即使是 Storyboard 和 Xib 之間蹭越,似乎也還是多多少少有一些紛爭响鹃。
Storyboard & Xib
公司 (年久失修) 的 iOS Guidelines 中寫著一句話:
進行源碼管理時 Storyboard 極易導(dǎo)致沖突案训,團隊開發(fā)時,各畫面與各組件盡可能使用 Xib 進行實現(xiàn)忿项。
對此我一直抱著贊否兩論的觀點。在實際工作時寞酿,同一 Storyboard 中存在大量 ViewController 十分容易沖突是一個不爭的事實脱柱,掉進這個坑的人有可能還進行過 xml 修正。但是這個鍋 Storyboard 不背惨好。一部分人可能因此選擇了棄 Storyboard 從 Xib 之路随闺,我也一度徘徊是否這才是正道。但是很顯然的是逗鸣,Storyboard 從一開始就不是為了代替 Xib 而來绰精。
除了 UI 設(shè)置的相似部分以外笨使,Storyboard 更重視畫面之間的關(guān)聯(lián)和遷移,而 Xib 作為通用組件的模版應(yīng)該是不二的選擇硫椰。
在 P-OOP 中靶草,將會存在大量的 dialog,盡管可以很容易的使用 Present Modally 來實現(xiàn)裕寨,不過為了保持系列產(chǎn)品的風(fēng)格一致性派继,需要考慮如何以比較好的方式來實現(xiàn)共通的 header 和 footer 樣式∏烀ǎ考慮過很多方案绅络,比如:
將 footer 和 header 集成在同一個 view 中嘁字,并添加一個 content view纪蜒,最終在某 controlelr view 中將上述 view 與實際從另一個 xib 中載入的 content view 組合寻行,完成組裝。但是存在一個比較顯著的缺點杆烁,實際可見的 controller view 所呈現(xiàn)的內(nèi)容并不是很直觀简卧,果然還是必須看代碼才能梳理清楚。
將 footer 和 header 以及一個 content view 集成在同一個 controller view 中析校。在代碼中按照要求載入 content铜涉,代理方法之類變得容易管理了一些,但是更糟糕的是這個 controller 的代碼終將成為垃圾場的吊奢。纹烹。。那加入繼承呢铺呵?有些小題大做?
果然簡潔才是最高的幻林,將 footer 和 header 完全獨立為兩個 view宴卖,按需載入症昏。結(jié)合 @IBInspectable 和 @IBDesignable 可以說是比較完美了父丰,從畫面設(shè)計到遷移等都很清晰掘宪。不足一提的小缺點是使用時候的 auto layout 的設(shè)置可能存在一些重復(fù)操作 (比如 Auto Layout 之類的)攘烛,若考慮 Model 除了 form sheet 以外可以是 full screen坟漱,后者需要在頂部額外預(yù)留 20px,這樣一來反而變得巧妙了腥寇。
也許過幾天自己的想法又發(fā)生了細微變化觅捆,但簡潔清晰無論何時都不會太壞。
心得
Storyboard Reference
Storyboard 容易引發(fā)沖突掂摔,這句話在 Storyboard Reference 面前是不成立的赢赊。
Storyboard Reference 第一次出現(xiàn)在 Xcode 7释移,可以從組件庫中找到它,并自行進行配置和關(guān)聯(lián)趋观,十分簡單锋边,無需贅述。即使是一個已經(jīng)完成且十分繁雜的 Storyboard剩辟,也可以選中想要分離的 Storyboard贩猎,通過 Editor -> Refactor to Storyboard 來實現(xiàn)萍膛。比如,使用了兩個 Container View艇棕,默認情況下此時畫面中存在三個 controller,對其進行分離之后沼琉,變成了這樣:
Loadable Nib
將 Xib 組件的載入?yún)f(xié)議化打瘪,其中一個目的是為了類型安全闺骚,另一個目的是為了減少重復(fù)代碼。
protocol Loadable: class {
static var nibName: String { get }
}
extension Loadable {
static var nibName: String { return String(describing: Self.self) }
}
對 UIView
進行擴展借杰,要求被載入的 view 遵循 Loadable
協(xié)議:
extension UIView {
func instantiateFromNib<T: UIView>(_:T.Type) -> T where T: Loadable {
if let nib = UINib(nibName: T.nibName, bundle: nil).instantiate(withOwner: nil, options: nil).first as? T {
return nib
} else {
fatalError("Nib \(T.nibName) is not exist ?!")
}
}
func instantiateFromNibOwner<T: UIView>(_:T.Type) where T: Loadable {
let bundle = Bundle(for: type(of: self))
if let nib = UINib(nibName: T.nibName, bundle: bundle).instantiate(withOwner: self, options: nil).first as? UIView {
nib.frame = self.bounds
nib.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(nib)
} else {
fatalError("Nib \(T.nibName) is not exist ?!")
}
}
}
簡潔的初始化:
let view:ClassName = self.instantiateFromNib(ClassName.self)
self.instantiateFromNibOwner(ClassName.self)
后來發(fā)現(xiàn)一個名為 Reusable 的庫蔗衡,其中除了這一部分的實現(xiàn)之外绞惦,還有對 Cell 甚至是 Storyboard 和 ViewController 的重用洋措,十分強大。
回到這一部分的實現(xiàn)菠发,略有區(qū)別的地方在于:
- Reusable 在初始化 nib 的時候選擇了擴展協(xié)議王滤。
- 在 File's Owner 的情況下,Reusable 使用了 Auto Layout滓鸠。由于我們的 P-OOP 項目對應(yīng)的設(shè)備尺寸不多雁乡,所以像是部分彈出框就沒有對應(yīng) Auto Layout,所以就直接從 frame 的尺寸下手了糜俗。踱稍。
追記:把這部分實現(xiàn)和例子提了出來放在了 Github 上~
@IBDesignable 和 @IBInspectable
@IBDesignable 可以用于視圖的實時渲染,@IBInspectable 可以用于定義運行時屬性悠抹。
舉個例子來說:首先在定義一個 DialogHeaderView
珠月,標記為 @IBDesignable
,將它的 headerTitle
屬性設(shè)置為 @IBInspectable
:
@IBDesignable class DialogHeaderView: UIView {
@IBInspectable var headerTitle: String = "" {
didSet {
navigationBar.topItem?.title = self.headerTitle
}
}
...
}
然后向目標視圖添加一個 UIView啤挎,并將類定義為 DialogHeaderView
,此時在 Attribuite Inspector 中可以直接設(shè)置屬性:
之后即會反映在運行時屬性欄中:
不過構(gòu)建失敗的時候還是挺多的卵凑,不妨通過 Editor -> Debug Selected Views 來調(diào)試一下選中的視圖侵浸。
類型安全
除了定義上面的 Loadable
協(xié)議旺韭,在類型安全這個問題上還可以進一步再做一些工作氛谜。
存在 Storyboard掏觉,Segue 的定義也就會有存在,由于 identifier 的定義是字符串值漫,防不勝防澳腹,不匹配的情況還是會時而發(fā)生。這時候使用 R.swift 就能夠完全解消這個擔(dān)憂了杨何。
R.swift 被廣泛使用于解決類型安全的問題酱塔,圖片、字體危虱、本地化等等都受益于此羊娃。