事情是这样的。
我最近在尝试用 SwiftUI 做一个相对复杂的应用,想看看不使用 UIKit 的转化工具,也就是不用 UIViewRepresentable
和 UIViewControllerRepresentable
,仅靠 SwiftUI 自己能做到什么程度。
刚开始的时候,SwiftUI 写起来的确很爽。DSL 形式的代码让我可以快速地把模糊的概念转化成 UI。但是项目逐渐复杂起来后,就会遇到各种小的问题,比如子视图不更新、手势没反应之类的。这些问题基本可以靠 stackoverflow 或者国外的一些博客解决,它们也在我的预期之中,毕竟资料不详细也应该是新框架的通病。
直到今天,在遇到了这样的一个 bug,我有点忍不住了...
为了展现这个 bug,让我们先写一段正常的代码:
class Team: ObservableObject {
@Published var members: [Soldier] = [Soldier(name: "Soldier 0")]
func addSoldier() {
members.append(Soldier(name: "Soldier \(members.count)"))
}
func removeCurrentSoldier() {
if members.count > 1 {
members.removeLast()
}
}
}
struct Soldier {
var name: String
}
struct ContentView: View {
@ObservedObject var team = Team()
var body: some View {
VStack {
ForEach(team.members.indices, id: \.self) { i in
TextField("", text: $team.members[i].name)
}
HStack {
Button("+") { team.addSoldier() }
Button("-") { team.removeCurrentSoldier() }
}
}
}
}
这段代码应该很好理解。ContentView
会展示 team
中保存的士兵小队的名单,通过下面的两个按钮可以增减小队的成员,在 TextField
中也可以更新成员的名字。运行起来大致像这样:
在上面这段代码的基础上,如果我们要给每个士兵加一个头像,把
TextField("", text: $team.members[i].name)
改为:
HStack {
Image(systemName: "person.fill")
TextField("", text: $team.members[i].name)
}
再次运行程序,尝试删除操作,程序会直接报错退出。。。
甚至当我们不加上这个 Image
,仅仅是在 TextField
外面套一个 HStack
或 VStack
,也会出现同样的问题。在 stackoverflow 也有不少人遇到了类似的问题,但回答中提出的 work around 都不适用。
为什么在外面套一个布局容器会影响控件的行为呀!
如果是一般的开源 UI 框架,出现这种奇葩问题我们可以跑去社区提 issue,或者尝试自己用源码修 bug。但是因为 SwiftUI 是个闭源的框架,貌似就只能给苹果官方提 bug report 了... 这种情况下的反馈速度,实在是不敢让人有所期望... 最高效的方法可能是改掉自己的设计,然后祈求新的方案不会再遇到类似的错误。
虽然我应该还是会继续完成手头的这个项目,但是这个 bug 极大损伤了我对 SwiftUI 在短期内能够实际应用的信心,因为这样的问题说明 SwiftUI 缺少的可能不仅仅是完善的文档与配套工具,它内部的一些核心机制还处在不太稳定的状态。在这样的框架上制作应用,无疑是在刀尖上跳舞。不过另一个角度,如果苹果没有很快的修复这些潜在的问题,可能社区中会出现一系列为 SwiftUI 定制的用于维护状态,从而间接调控生命周期的库,所以说不好这也是开发者们的一些机会。
本文就到这里了,如果你也觉得这个 bug 有点匪夷所思,不妨点个赞~