本文从新手常犯的布局bug,推导出更加适用移动客户端的开发框架,从而得出Flutter和SwiftUI是更加适合客户端开发的结论。
(一)问题描述
在开发过程中,我们经常会遇到这样的逻辑,A和B两个UI,他们之间的布局是有关联的,
比如A如果要展示,B的位置就要相应的调整。
(二)bad case新手示范
如果是新手,很容易根据产品文档的描述写出类似下面逻辑的代码:
if A.isHidden == NO
B.frame = xxx
这类代码初看是没有问题的,但实际其背后隐藏着巨大的隐患。
我们对上面的方法进行扩展如下:
- (void)udpateAState:(BOOL)hidden {
A.hidden = hidden;
}
- (void)updateBState {
if A.isHidden == NO
B.top = A.top
else
B.top = Navigationbar.height
}
这样你就会发现,本来没什么关联的两个方法: udpateAState
和 updateBState
,在我们的逻辑安排下必须要求先后顺序了,
也即:调用 udpateAState
之后,必须调用 updateBState
,当然你可能说把 updateBState
放在 udpateAState
方法里不就行了吗?
但实际review代码的时候,竟然遇到忘记绑定导致的各种时序问题。
所以这个方案本质上就是行不通的,即使暂时行得通,其维护成本和后续复杂度也是非常高的(我们只举例了2个UI的关联,如果是3个、4个甚至10个)。
那这类问题一般怎么处理呢?
这类问题比较有效的处理成套方案是: 【数据驱动 + UI约束】。
(三)数据驱动 + UI约束
如果你是Flutter或者SwiftUI开发者,你会很熟悉【数据驱动 + UI约束】这一套开发模式。
相比我们上面描述的写法,A和B这两个UI将不再进行数据上的依赖,而是同事和数据进行关联起来,比如:
// KVO showAState state
self.showAState = NO
if self.showAState
B.top = A.top
else
B.top = Navigationbar.height
这样写的好处是只要我们修改数据的状态,那么就会同步更新有相关依赖的UI控件,也就是【数据驱动】。
【数据驱动】这一套在Flutter中是默认的设计方式,Flutter中相关代码如下:
setState {
showAState = false
}
在 setState
中包裹的状态量如果有变化,那么Flutter就会自动刷新UI。
解决了UI之间状态依赖的问题,但接下来还有个问题,就是如下的逻辑:
if self.showAState
B.top = A.top
else
B.top = Navigationbar.height
你会发现UI之间存在着布局的依赖,对于布局依赖我们自然要拿出【UI约束】这套对口的解决方案。
如果你写过Flutter或者SwiftUI的布局代码,你会发现他们号称的声明式UI的写法,本质上是在底层完成了UI的自动约束。
(四)小结
可以发现,移动客户端发展到现在这个阶段,Flutter和SwiftUI的诞生并不只是语言上更加安全,在设计模式上也更加契合【数据+UI】的客户端布局方式。