自從入坑了 Flutter,瞭解了現代 web 框架,回頭來看 iOS 原生的命令式 UI 產能實在過低了,就好像騎自行車和汽車賽跑同樣。git
setState()
,這一點能夠經過 RxSwift 的綁定來將就。命令式的問題在陶文的 面向對象不是銀彈,DDD 也不是,TypeScript 纔是 中有更深刻的討論:程序員
Many states:數量上多
Concurrent / Parallel:併發是邏輯上的,並行是物理上的。不管是哪一種,都比 sequential 更復雜。
Long range causality:長距離的因果關係
Entangled:剪不斷理還亂github
雖然 SwiftUI 很美,甚至支持了 Hot reload,可是遠水解不了近渴,iOS 13+ 的最低門檻把國內大多 App 擋在門外,如同之前的 UIStackView 同樣幾年內高不可攀。web
由於去年 App 終於升級了最低支持 iOS 9,因此 安利了一波 UIStackView ,它確實是實現了很多 FlexBox 的功能,可是 StackView 真的是聲明式嗎?swift
headerStackView.axis = .horizontal headerStackView.addArrangedSubviews([headerLeftLine, headerLabel, headerRightLine]) headerStackView.alignment = .center headerStackView.snp.makeConstraints { $0.centerX.equalToSuperview() } 複製代碼
只能勉強說有一點聲明式的意思吧。bash
UIStackView 其實足夠強大,問題就出在調用層的不夠友好,若是讓它長着 Flutter/Dart 同樣的臉,也許還能一戰。markdown
build()
入口 和更新方法 rebuild()
Row/Column
, Spacer
(sizedBox
in Flutter)ListView
(UITableView
in UIKit)Padding
Center
SizedBox
GestureDetector
最低版本: iOS 9
依賴:UIKit併發
建議使用 Then 來作初始化的語法糖。
這套封裝的另外一個目標是減小或者消滅直接使用約束的場景app
繼承 DeclarativeViewController
或者 DeclarativeView
框架
class ViewController: DeclarativeViewController { ... } 複製代碼
重寫 build()
函數,返回你的 UI,和 Flutter 相似。
這個 View 會被加到 ViewController 的 view 上,而且全屏化。
override func build() -> DZWidget { return ... } 複製代碼
橫向佈局 同 Flutter 的 Row
DZRow( mainAxisAlignment: ... // UIStackView.Distribution crossAxisAlignment: ... // UIStackView.Alignment children: [ ... ]) 複製代碼
縱向佈局 同 Flutter 的 Column
DZColumn( mainAxisAlignment: ... // UIStackView.Distribution crossAxisAlignment: ... // UIStackView.Alignment children: [ ... ]) 複製代碼
內填充 同 Flutter 的 Padding
DZPadding( edgeInsets: DZEdgeInsets.only(left: 10, top: 8, right: 10, bottom: 8), child: UILabel().then { $0.text = "hello world" } ), 複製代碼
DZPadding( edgeInsets: DZEdgeInsets.symmetric(vertical: 10, horizontal: 20), child: UILabel().then { $0.text = "hello world" } ), 複製代碼
DZPadding( edgeInsets: DZEdgeInsets.all(16), child: UILabel().then { $0.text = "hello world" } ), 複製代碼
autolayout 的 centerX 和 centerY
DZCenter( child: UILabel().then { $0.text = "hello world" } ) 複製代碼
寬高約束
DZSizedBox( width: 50, height: 50, child: UIImageView(image: UIImage(named: "icon")) ) 複製代碼
佔位空間
對於 Row
: 同 Flutter 的 SizedBox
設置 width
.
DZRow(
children: [
...
DZSpacer(20),
...
]
)
複製代碼
對於 Column
: 同 Flutter 的 SizedBox
設置 height
.
DZColumn(
children: [
...
DZSpacer(20),
...
]
)
複製代碼
列表
隱藏了 delegate/datasource
和 UITableViewCell
的概念
靜態表格
DZListView( tableView: UITableView().then { $0.separatorStyle = .singleLine }, sections: [ DZSection( cells: [ DZCell( widget: ..., DZCell( widget: ..., ]), DZSection( cells: [ DZCell(widget: ...) ]) ]) 複製代碼
動態表格
return DZListView( tableView: UITableView(), cells: ["a", "b", "c", "d", "e"].map { model in DZCell(widget: UILabel().then { $0.text = model }) } ) 複製代碼
是 Flutter stack, 不是 UIStackView
,用來處理兩個頁面的疊加
DZStack( edgeInsets: DZEdgeInsets.only(bottom: 40), direction: .horizontal, // center direction base: YourViewBelow, target: YourViewAbove ) 複製代碼
支持點擊事件(child 是 UIView 調用 TapGesture, UIButton 調用 touchUpInside)
支持遞歸查找,也就是說傳入的 child 能夠是嵌套不少層的 DZWidget
DZGestureDetector( onTap: { print("label tapped") }, child: UILabel().then { $0.text = "Darren"} ) DZGestureDetector( onTap: { print("button tapped") }, child: UIButton().then { $0.setTitle("button", for: UIControl.State.normal) $0.setTitleColor(UIColor.red, for: UIControl.State.normal) }), 複製代碼
支持設置導航欄,這個控件只是一個配置類
DZAppBar( title: "App Bar Title", child: ... ) 複製代碼
重刷
self.rebuild { self.hide = !self.hide } 複製代碼
增量刷新
UIView.animate(withDuration: 0.5) { // incremental reload self.hide = !self.hide self.context.setSpacing(self.hide ? 50 : 10, for: self.spacer) // 支持改變區間距離 self.context.setHidden(self.hide, for: self.label) // 支持隱藏 } 複製代碼
這套輕量封裝已經減輕了很多我平常寫 UI 的認知負擔,提升很多的產能。(程序員爲了犯懶什麼苦都能吃)
雖然作不到 Flutter 那種 Widget Tree 隨便換,Element Tree 狂優化來兜底,可是對於相對靜態的頁面,佈局變化不大的話,這層封裝仍是勝任的。(就是寫法 Fancy 一點的 UITableView/UIStackView
而已)
若是你也以爲有用,歡迎一塊兒來完善。
GitHub 地址: DeclarativeSugar