工作是这样的。

我最近在测验用 SwiftUI 做一个相对杂乱的应用,想看看不使用 UIKit 的转化东西,也就是不用 UIViewRepresentableUIViewControllerRepresentable,仅靠 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 中也可以更新成员的名字。运转起来大致像这样:

2021-03-12 17.33.13.gif

在上面这段代码的基础上,假如咱们要给每个战士加一个头像,把

TextField("", text: $team.members[i].name)

改为:

HStack {
Image(systemName: "person.fill")
TextField("", text: $team.members[i].name)
}

再次运转程序,测验删除操作,程序会直接报错退出。。。

2021-03-12 17.40.28.gif

甚至当咱们不加上这个 Image,仅仅是在 TextField 外面套一个 HStackVStack,也会呈现相同的问题。在 stackoverflow 也有不少人遇到了类似的问题,但回答中提出的 work around 都不适用。

为什么在外面套一个布局容器会影响控件的行为呀!

假如是一般的开源 UI 结构,呈现这种奇葩问题咱们可以跑去社区提 issue,或许测验自己用源码修 bug。但是由于 SwiftUI 是个闭源的结构,貌似就只能给苹果官方提 bug report 了… 这种情况下的反馈速度,实在是不敢让人有所期望… 最高效的办法或许是改掉自己的规划,然后祈求新的方案不会再遇到类似的过错。

尽管我应该仍是会继续完成手头的这个项目,但是这个 bug 极大损伤了我对 SwiftUI 在短期内可以实际应用的信心,由于这样的问题阐明 SwiftUI 缺少的或许不仅仅是完善的文档与配套东西,它内部的一些中心机制还处在不太稳定的状态。在这样的结构上制造应用,无疑是在刀尖上跳舞。不过另一个角度,假如苹果没有很快的修复这些潜在的问题,或许社区中会呈现一系列为 SwiftUI 定制的用于维护状态,然后间接调控生命周期的库,所以说不好这也是开发者们的一些时机。

本文就到这里了,假如你也觉得这个 bug 有点匪夷所思,不妨点个赞~