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

Posted

技术标签:

【中文标题】使用 ForEach 插入的 SwiftUI 视图未随动画更新【英文标题】:SwiftUI views inserted with ForEach not updating with animation 【发布时间】:2020-09-22 05:18:01 【问题描述】:

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

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 的调用。

在 CustomButton 调用的末尾添加 .id(UUID()) 修复了以下问题:

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

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

【问题讨论】:

【参考方案1】:

为了让 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

【讨论】:

一旦你知道了正确的答案,那就太容易了。感谢您的回复,这让我很生气,解决方案非常简单!声明式编程是思维的一大转变。

以上是关于使用 ForEach 插入的 SwiftUI 视图未随动画更新的主要内容,如果未能解决你的问题,请参考以下文章

我无法在 SwiftUI 中使用 foreach 呈现我的视图

SwiftUI:如何根据 Picker 的值更新 ForEach 循环的范围

使用 ForEach 分层视图并使用 ZStack 偏移以创建一堆扑克筹码 (SwiftUI)

SwiftUI - 使用 ForEach 时,您应该在子视图中使用 `@State var` 还是 `let`

SwiftUI,@Published 和 ForEach 没有更新我的视图

SwiftUI:列表视图与 ForEach 视图