用SwiftUI在列表中实现行变化的动画效果

huangapple go评论59阅读模式
英文:

Animating row change in a List with SwiftUI

问题

以下是翻译好的内容:

专注于理解各种动画行为的基本概念,我有兴趣以编程方式实现在列表中更改行的动画效果。

我可以在以下示例中实现它:

struct First: View {
  @State var items = ["One", "Two", "Three", "Four"]

  var body: some View {
    VStack {
        List(items, id: \.self) { item in
            Text(item)
        }
        
        Button("点击我") {
            withAnimation {
                items.swapAt(2, 0)
            }
        }
    }
 }
}

正如您在按钮上点击后所看到的,我们可以在单元格中获得一种漂亮的交换动画效果。我们可以看到,单元格实际上正在移动,一个向上,另一个向下。

当我将列表代码更改为以下内容时:

List(items.indices, id: \.self) { index in
   Text(items[index])
}

正如您所看到的,之前的动画不再起作用,而是出现了文本更改动画。

是否有解决方案可以保留第一个(默认)的交换动画?

谢谢。

英文:

Focusing on comprehending the underlying concept of the various animation behaviors, I am interested of programmatically animate the change of a row in a List.

I am able to achieve it in the following example:

struct First: View {
  @State var items = ["One", "Two", "Three", "Four"]

  var body: some View {
    VStack {
        List(items, id: \.self) { item in
            Text(item)
        }
        
        Button("Hit me") {
            withAnimation {
                items.swapAt(2, 0)
            }
        }
    }
 }
}

As you can see upon tapping on the button, we receive a nice swapping animation in the cells. We can see that cells are actually moving, one to the top and the other downwards:

用SwiftUI在列表中实现行变化的动画效果

When I change the list code to the following:

List(items.indices, id: \.self) { index in
   Text(items[index])
}

As you can see the prior animation doesn't work anymore and instead I receive a text change animation:

用SwiftUI在列表中实现行变化的动画效果

Is there a solution to keep the first (default) swapping animation ?

Thanks.

答案1

得分: 1

这是一个身份的问题。SwiftUI通过它们的身份跟踪视图元素,当单个元素的身份保持不变时,它才能在状态之间进行动画。

在第一个代码中,List 的 id 是 String 类型,字符串的位置变化了,但保持了相同的 id。

在第二个代码中,List 的 id 是 Index 类型,但当交换索引时,索引不会改变,只是该索引处的相对字符串发生了变化。

所以,结果 1. 根据字符串 id 进行位置动画,2. 根据索引 id 进行字符串动画。

但是,你可以明确告诉SwiftUI要使用哪个id,即使在第二个代码中,像这样:

List(items.indices, id: \.self) { index in
    Text(items[index]).id(items[index]) // 这里
}
英文:

It's a question of identity. SwiftUI keeps track of view elements by their identity, and it can only animate between states when the identities of the single elements stay the same.

In the first code, the List id is of type String, the string changes place but keeps the same id.

In the second code the List id is of type Index, but when swapping the index does not change, only the relative string at that index.

So in result 1. animates the position based on string id, and 2. animates the string based on index id.

BUT you can tell SwiftUI explicitly which id to use, also in the second code, like this:

            List(items.indices, id: \.self) { index in
                Text(items[index]).id(items[index]) // Here
            }

答案2

得分: 1

以下是您要翻译的内容:

"你不能在 ForEach 中使用索引,它是一个 View 而不是一个循环。第一个问题是动画,正如您所看到的,下一个问题是当您删除项目时会崩溃。

当您的列表项是一个在 @State 中的值类型数组时,您需要传递一个有效的 id 参数,例如:

List(items, id: \.uniqueIdentifier) { item in

原因是这样 List 可以跟踪插入、移除和删除。与对象不同,值类型没有固有的身份,因为它们被复制到每个地方,而不是复制引用。

大多数 SwiftUI 视图,比如 List,都使用 Identifiable 协议来简化这个过程,例如:

struct Item: Identifiable {
    let id = UUID() // 这也可以是一个计算属性,它将一堆属性连接在一起,以生成这个 Item 的唯一字符串。

    let text: String
}

struct First: View {
  @State var items = [Item(text: "One"), Item(text: "Two"), Item(text: "Three"), Item(text: "Four")]

  var body: some View {
    VStack {
        List(items) { item in // 当项目符合 Identifiable 时,不需要 id 参数
            Text(item.text)
        }
    ...
英文:

You can't use indices with the ForEach, it's a View not a for loop. The first problem is the animations as you saw, the next problem is it will crash when you remove items.

When your list items are an array of value types in an @State you need to pass a valid id param, e.g.

List(items, id: \.uniqueIdentifier) { item in

The reason is so the List can track the inserts, removes and deletes. Value types don't have inherent identity like objects do because they are copied everywhere rather than a reference being copied.

Most SwiftUI Views that work on arrays like List make use of the Identifiable protocol to make this simpler, e.g.

struct Item: Identifiable {
    let id = UUID() // this could also be a computed var that concatenates a bunch of properties that result in a unique string for this Item.

    let text: String
}

struct First: View {
  @State var items = [Item(text: "One"), Item(text: "Two"), Item(text: "Three"), Item(text: "Four")]

  var body: some View {
    VStack {
        List(items) { item in // no id param needed when the item conforms to Identifiable
            Text(item.text)
        }
    ...

huangapple
  • 本文由 发表于 2023年5月11日 17:53:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76226338.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定