在现有动画为 运行 时为 SwiftUI 状态更改设置动画会导致 Form 处于不一致状态

Animating a SwiftUI state change while existing animation is running causes Form to be in an inconsistent state

我正在尝试实现一个文本字段,在用户输入时更新搜索结果。我已经尝试过同步(每次击键后更新搜索结果)和异步。两者都会导致一些非常奇怪的动画状态发生。

事情是这样的。我先慢慢打字,然后慢慢退格,然后一切正常。然后我快速输入并快速退格。

我已将我的观点简化为:

import SwiftUI

struct ContentView: View {
  @State var elements: [Element] = []
  @State var text = ""
  
  var body: some View {
    Form {
      TextField("Text", text: $text)
        .onChange(of: text, perform: { newText in
          withAnimation {
            elements = (0..<newText.count).map { i in
              Element(id: i, value: "\(i)")
            }.shuffled()
        }
        })

      ForEach(elements) { element in
        Text(element.value)
      }
      
      Text("Should have \(elements.count) elements")
    }
  }
}

struct Element: Identifiable {
  var id: Int
  var value: String
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

如果您在预览、模拟器或设备上的文本字段中输入几个字符,然后按住退格键,表单将处于不一致状态。很多时候,即使文本字段为空,仍然有一个值为“0”的文本元素。我还看到过“应该有 X 元素”文本位于“0”文本元素行上方的情况。发生这种情况后,更改状态时将不会再发生动画。

问题似乎出在我更改 elements 状态时,而现有动画是 运行,它正在动画先前的 elements 状态更改。如果您在文本字段中缓慢键入并缓慢退格,则不会出现此问题。

如果我使用列表而不是表单,也会发生这种情况。如果您使用 VStack,则不会发生这种情况。

编辑:

为了尽可能多地删除变量,我缩小了我的示例(见下文)。问题仍然存在。如果您输入一堆字符,然后按住退格键,底部的文本将显示“应该有 0 个元素”,但列表中仍有包含“旧”数据的行。这是在实际设备上的 iOS 14.4 上发生的。

我看到控制台中弹出此消息:

2021-01-27 12:01:33.231810-0500 Animations[1220:293840] [Snapshotting] Snapshotting a view (0x10410d190, _UIReplicantView) that has not been rendered at least once requires afterScreenUpdates:YES.

import SwiftUI

struct ContentView: View {
  @State var text = ""
  
  var body: some View {
    TextField("Text", text: $text)
      .textFieldStyle(RoundedBorderTextFieldStyle())
      .padding()

    List {
      ForEach(elements) { element in
        Text(element.value)
      }
    }
    .animation(.default)

    Text("Should have \(elements.count) elements")
      .background(Color(.systemRed))
      .padding()
  }
  
  private var elements: [Element] {
    (0..<text.count).map { i in
      Element(id: i, value: "\(i)")
    }.shuffled()
  }
}

struct Element: Identifiable {
  var id: Int
  var value: String
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

最近刚刚对此进行了测试,发现此问题不再发生(至少从 iOS 15.3 开始,但可能是整个 iOS 15)。