0

我正在尝试向我的 中添加一个删除动画ForEach,以便它Card内部的每个动画在被删除时都会扩大。这是我到目前为止所拥有的:

问题是,无论Card按下哪个,它总是最后一个动画。有时,每张卡片内的文字都有一个奇怪的滑动/变形动画。这是我的代码:

/// Ran into this problem: "SwiftUI ForEach index out of range error when removing row"
/// `ObservableObject` solution from https://stackoverflow.com/a/62796050/14351818
class Card: ObservableObject, Identifiable {
    let id = UUID()
    @Published var name: String

    init(name: String) {
        self.name = name
    }
}

struct ContentView: View {
    @State var cards = [
        Card(name: "Apple"),
        Card(name: "Banana "),
        Card(name: "Coupon"),
        Card(name: "Dog"),
        Card(name: "Eat")
    ]
    
    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                
                ForEach(cards.indices, id: \.self) { index in
                    CardView(card: cards[index], removePressed: {
                        
                        withAnimation(.easeOut) {
                            _ = cards.remove(at: index) /// remove the card
                        }
                        
                    })
                    .transition(.scale)
                }
                
            }
        }
    }
}

struct CardView: View {
    
    @ObservedObject var card: Card
    var removePressed: (() -> Void)?
    
    var body: some View {
        Button(action: {
            removePressed?() /// call the remove closure
        }) {
            VStack {
                Text("Remove")
                Text(card.name)
            }
        }
        .foregroundColor(Color.white)
        .font(.system(size: 24, weight: .medium))
        .padding(40)
        .background(Color.red)
    }
}

我怎样才能扩大Card被点击的,而不是最后一个?

4

3 回答 3

2

您看到此行为的原因是因为您将索引用作idfor ForEach。因此,当从cards数组中删除一个元素时,唯一的区别ForEach是最后一个索引消失了。

您需要确保id唯一标识ForEach.

如果必须使用索引并标识每个元素,则可以使用该enumerated方法或zip数组及其索引。我喜欢后者:

ForEach(Array(zip(cards.indices, cards)), id: \.1) { (index, card) in 
   //...
}

以上使用对象本身作为ID,这需要符合Hashable. 如果你不想这样,你可以id直接使用该属性:

ForEach(Array(zip(cards.indices, cards)), id: \.1.id) { (index, card) in
  //...
}

为了完整起见,这是enumerated版本(从技术上讲,它不是索引,而是偏移量,但对于基于 0 的数组,它是相同的):

ForEach(Array(cards.enumerated()), id: \.1) { (index, card) in 
   //...
}
于 2021-04-04T18:11:41.040 回答
1

New Dev 的回答很棒,但我还有其他需要的东西。在我的完整代码中,每个里面都有一个按钮Card,可以滚动ScrollView到最后。

/// the ForEach
ForEach(Array(cards.enumerated()), id: \.1) { (index, card) in
    CardView(
        card: cards[index],
        scrollToEndPressed: {
            proxy.scrollTo(cards.count - 1, anchor: .center) /// trying to scroll to end... not working though.
        },
        removePressed: {
            
            withAnimation(.easeOut) {
                _ = cards.remove(at: index) /// remove the card
            }
            
        }
    )
    .transition(.scale)
}

/// CardView
struct CardView: View {
    
    @ObservedObject var card: Card
    var scrollToEndPressed: (() -> Void)?
    var removePressed: (() -> Void)?
    
    var body: some View {
        VStack {
            Button(action: {
                scrollToEndPressed?() /// scroll to the end
            }) {
                VStack {
                    Text("Scroll to end")
                }
            }
            
            Button(action: {
                removePressed?() /// call the remove closure
            }) {
                VStack {
                    Text("Remove")
                    Text(card.name)
                }
            }
            .foregroundColor(Color.white)
            .font(.system(size: 24, weight: .medium))
            .padding(40)
            .background(Color.red)
        }
    }
}

使用上面的代码,“滚动到结束”按钮不起作用。

我通过ID为每个CardView.

ForEach(Array(cards.enumerated()), id: \.1) { (index, card) in
    CardView(card: cards[index], scrollToEndPressed: {
        
        withAnimation(.easeOut) { /// also animate it
            proxy.scrollTo(cards.last?.id ?? card.id, anchor: .center) /// scroll to the last card's ID
        }
        
    }, removePressed: {
        
        withAnimation(.easeOut) {
            _ = cards.remove(at: index) /// remove the card
        }

    })
    .id(card.id) /// add ID
    .transition(.scale)
}

结果:

于 2021-04-04T19:25:13.593 回答
1

我建议您重新考虑并使用 Card 作为结构而不是,并确认IdentifiableEquatable

struct Card: Hashable, Identifiable {
    let id = UUID()
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

然后创建一个持有你卡片的视图模型。

class CardViewModel: ObservableObject {
    @Published
    var cards: [Card] = [
        Card(name: "Apple"),
        Card(name: "Banana "),
        Card(name: "Coupon"),
        Card(name: "Dog"),
        Card(name: "Eat")
    ]
}

迭代cardViewModel.cards并传递cardCardView. 使用removeAllArray 的方法而不是remove. 它是安全的,因为Cards 是唯一的。

ForEach(viewModel.cards) { card in
   CardView(card: card) {
        withAnimation(.easeOut) {
          cardViewModel.cards.removeAll { $0 == card}
       }
   }       
}

一个完整的工作示例。

struct ContentView: View {
    @ObservedObject var cardViewModel = CardViewModel()
    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                
                ForEach(cardViewModel.cards) { card in
                    CardView(card: card) {
                        withAnimation(.easeOut) {
                            cardViewModel.cards.removeAll { $0 == card}
                        }
                    }
                    .transition(.scale)
                }
                
            }
        }
    }
}

struct CardView: View {
        
    var card: Card
    
    var removePressed: (() -> Void)?

    
    var body: some View {
        Button(action: {
            removePressed?()
        }) {
            VStack {
                Text("Remove")
                Text(card.name)
            }
        }
        .foregroundColor(Color.white)
        .font(.system(size: 24, weight: .medium))
        .padding(40)
        .background(Color.red)
    }
}





如果由于某种原因您需要卡片索引ContentView,请执行此操作。

  1. 访问和操作 EnvironmentObject 中的数组项

  2. 从 For Each 循环生成的子视图中删除项目会导致致命错误

两者都类似于 Apple 的这个教程。

https://developer.apple.com/tutorials/swiftui/handling-user-input

于 2021-04-04T20:07:54.897 回答