在 SwiftUI 中,当调用的方法中发生变化时,如何一次对它们进行动画处理?

In SwiftUI how do I animate changes one at a time when they occur in a called method?

虽然我在点击按钮时得到了一个动画,但这不是我想要的动画。 整个视图被立即替换,但我想按顺序查看每个元素的变化。我在父视图和被调用方法中都尝试过。两者都没有产生预期的结果。

(这是原代码的简化版)

import SwiftUI

struct SequencedCell: Identifiable {
   let id = UUID()
   var value: Int

   mutating func addOne() {
      value += 1
   }

}

struct AQTwo: View {
   @State var cells: [SequencedCell]

   init() {
      _cells = State(initialValue: (0 ..< 12).map { SequencedCell(value: [=10=]) })
   }

   var body: some View {
      VStack {
         Spacer()
         Button("+") {
            sequencingMethod(items: $cells)
         }
         .font(.largeTitle)
         Spacer()

         HStack {
            ForEach(Array(cells.enumerated()), id: \.1.id) { index, item in
 //              withAnimation(.linear(duration: 4)) {
               Text("\(item.value)").tag(index)
 //              }
            }
         }
         Spacer()
      }
   }

   func sequencingMethod(items: Binding<[SequencedCell]>) {
      for cell in items {
         withAnimation(.linear(duration: 4)) {
            cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
           // cell.wrappedValue.addOne()
        }
      }
   }
}


struct AQTwoPreview: PreviewProvider {
   static var previews: some View {
      AQTwo()
   }
}

所以我希望 0 变成 1,然后 1 变成 2,等等

编辑: 虽然我已经接受了一个答案,但它回答了我的问题,但没有解决我的问题。

我不能使用 DispatchQueue.main.asyncAfter 因为我正在更新的值是一个 inout 参数,它让编译器不高兴:

Escaping closure captures 'inout' parameter 'grid'

所以我尝试了 Malcolm (malhal) 使用延迟的建议,但一切都立即发生,没有顺序动画(整个更新项目块作为一个动画)

这是我调用的递归方法:

   static func recursiveAlgorithm(targetFill fillValue: Int, in grid: inout [[CellItem]],
                                  at point: (x: Int, y: Int), originalFill: Int? = nil,  delay: TimeInterval) -> [[CellItem]] {
      /// make sure the point is on the board (or return)
      guard isValidPlacement(point) else { return grid }
      /// the first time this is called we don't have `originalFill`
      /// so we read it from the starting point
      let tick = delay + 0.2
      //AnimationTimer.shared.tick()
      let startValue = originalFill ?? grid[point.x][point.y].value
      if grid[point.x][point.y].value == startValue {
         withAnimation(.linear(duration: 0.1).delay(tick)) {
            grid[point.x][point.y].value = fillValue
                  }
         _ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x, point.y - 1), originalFill: startValue, delay: tick)
         _ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x, point.y + 1), originalFill: startValue, delay: tick)
         _ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x - 1, point.y), originalFill: startValue, delay: tick)
         _ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x + 1, point.y), originalFill: startValue, delay: tick)
      }
      return grid
   }

欢迎进一步comments/suggestions,因为我会继续努力解决这个问题。

如评论中所述,lowest-tech 版本可能只是使用 DisatpchQueue.main.asyncAfter 调用:

func sequencingMethod(items: Binding<[SequencedCell]>) {
    var wait: TimeInterval = 0.0
    
    for cell in items {
        DispatchQueue.main.asyncAfter(deadline: .now() + wait) {
            withAnimation(.linear(duration: 1)) {
                cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
            }
        }
        wait += 1.0
    }
}

您可以使用 delay(_:),例如

func sequencingMethod(items: Binding<[SequencedCell]>) {
       var delayDuration = 0.0
       for cell in items {
           withAnimation(.linear(duration: 4).delay(delayDuration)) {
              cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
        }
        delayDuration += 0.5
      }
   }