使用 ForEach 插入的 SwiftUI 视图未使用动画更新

SwiftUI views inserted with ForEach not updating with animation

我创建了一个插入三个按钮的简单函数。我想要一个单独的按钮在按下时旋转。我最初的尝试是这样的:

ForEach(0..<3, id: \.self) { number in {
    Button(action: {
            self.flagTapped(number: index)
            withAnimation(Animation.interpolatingSpring(stiffness: 15.0, damping: 3.0)) {
                animationAmount2 += 360
            }
    }) {
            Image(self.countries[index])
                 .renderingMode(.original)
    }
    .clipShape(Capsule())
    .overlay(Capsule().stroke(Color.black, lineWidth: 1)
    .shadow(color: .black, radius: 10, x: 3, y: 3)
    .rotation3DEffect(
    .degrees(animationAmount2),
         axis: (x: 0.0, y: 1.0, z: 0.0),
         anchor: .center,
         anchorZ: 0.0,
         perspective: 1.0
    )
 }

它有效,但问题是当您按下任何按钮时每个按钮都会动画,因为 animationAmount2 是一个@State 属性 所以当它更新时,每个按钮都会动画,而不仅仅是按下的按钮。

我的下一个想法是创建一个自定义按钮并在其中插入动画代码和属性,以便按钮可以单独设置动画。结果是:

func getFlagView(index: Int) -> some View {
    
    let flag = CustomButton(country: countries[index], index: index) { (Index) in
        flagTapped(number: index)
    }
    
    return flag
}

我现在在 ForEach 中调用这个函数,它完美地插入按钮,只有我按下的按钮旋转。问题是当视图刷新时它永远不会重绘按钮。 ForEach 正在执行,但它就像是忽略了对 getFlagView 的调用。

将 .id(UUID()) 添加到 CustomButton 调用的末尾修复了:

func getFlagView(index: Int) -> some View {
    
    let flag = CustomButton(country: countries[index], index: index) { (Index) in
        flagTapped(number: index)
    }.id(UUID())
    
    return flag
}

现在,当视图按预期刷新时,按钮会重绘,但动画不起作用。我真的不知道为什么添加 UUID 会破坏动画。

为了让 SwiftUI 为您的按钮设置动画,它需要能够观察到唯一标识视图的渲染之间的变化。在您的第一个案例中,您的视图具有 id 的 012。所以动画成功了。

当您应用 .id(UUID()) 时,这使按钮在每次绘制时 id 具有唯一性 。所以 SwiftUI 看不到您更改了按钮,因为每次 ForEach 执行时它总是将 3 个按钮视为 3 个全新的按钮。

您需要的是一个 id 来唯一标识每个按钮,但在国家/地区更改之前它不会更改。您需要一个 id 来唯一标识每个国家/地区,并且 id 是该国家/地区的名称。

将您的 getFlagView 更改为使用国家名称作为 id:

func getFlagView(index: Int) -> some View {
    
    let flag = CustomButton(country: countries[index], index: index) { (Index) in
        flagTapped(number: index)
    }.id(countries[index])
    
    return flag
}