自从入坑了 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