You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何删除已渲染的拖拽卡片实例并触发页面重绘

SwiftUI 实现Tinder风格左滑删除卡片方案

你当前代码无法触发删除后重绘的核心原因有两个:

  • 父视图的imageObjectGroup未声明为@State状态属性,数组变更无法触发视图刷新
  • 子视图CardView中用@State持有card实例,属于局部状态,修改不会同步到父视图的数据源,也没有提供删除操作的回调通道

以下是完整可运行的修改后代码:

1. ContentView 修改

import Photos
import SwiftUI

// 首先确保你的ImageObject实现了Identifiable协议,这是ForEach遍历的前提
struct ImageObject: Identifiable {
    let id = UUID()
    var x: CGFloat = 0
    var y: CGFloat = 0
    var degree: Double = 0
    // 补充你自己的其他业务属性即可
}

struct ContentView: View {
    // 将数据源声明为状态属性,数组变更自动触发重绘
    @State private var imageObjectGroup: [ImageObject] = [
        // 此处替换为你自己的卡片初始化数据
        ImageObject(), ImageObject(), ImageObject()
    ]
    
    var body: some View {
        VStack{
            ZStack{
                // 用$前缀拿到数组元素的Binding,同步传递给子视图
                ForEach($imageObjectGroup){ $card in
                    CardView(card: $card) {
                        // 接收子视图删除回调,从数据源中移除对应卡片
                        if let index = imageObjectGroup.firstIndex(where: { $0.id == card.id }) {
                            imageObjectGroup.remove(at: index)
                        }
                    }.padding(8)
                }
            }.zIndex(1.0)
        }
    }
}

2. CardView 修改

struct CardView: View{
    // 改用@Binding持有卡片,所有修改同步到父视图数据源
    @Binding var card: ImageObject
    // 新增删除回调闭包
    var onDelete: () -> Void
    // 修正原代码透明度参数错误,opacity取值范围为0-1
    let cardGradient = Gradient(colors: [Color.blue.opacity(0.25), Color.black.opacity(0.5)])
    
    var body: some View{
        ZStack(alignment: .leading){
            LinearGradient(gradient: cardGradient, startPoint: .top, endPoint: .bottom)
        }
        .offset(x: card.x, y: card.y)
        .rotationEffect(.init(degrees: card.degree))
        .gesture(
            DragGesture().onChanged { value in
                card.x = value.translation.width
                card.y = value.translation.height
                card.degree = 7 * (value.translation.width > 0 ? 1 : -1)
            }
            .onEnded { value in
                // iOS 17+ 支持withAnimation完成回调,等左滑动画结束后再执行删除
                withAnimation(.interpolatingSpring(mass: 1.0, stiffness: 50, damping: 8, initialVelocity: 0)) {
                    switch value.translation.width {
                    case 0...100, (-100)...(-1):
                        card.x = 0; card.degree = 0; card.y = 0
                    case let x where x > 100:
                        card.x = 200; card.degree = 12
                    case let x where x < -100:
                        card.x = -200; card.degree = -12
                    default:
                        card.x = 0; card.y = 0
                    }
                } completion: {
                    // 仅左滑触发阈值时执行删除
                    if value.translation.width < -100 {
                        onDelete()
                    }
                }
                
                // 若需要兼容iOS17以下版本,将上面的withAnimation块替换为以下代码即可
                /*
                withAnimation(.interpolatingSpring(mass: 1.0, stiffness: 50, damping: 8, initialVelocity: 0)){
                    switch value.translation.width {
                        case 0...100, (-100)...(-1):
                            card.x = 0; card.degree = 0; card.y = 0
                        case let x where x > 100:
                            card.x = 200; card.degree = 12
                        case let x where x < -100:
                            card.x = -200; card.degree = -12
                        default:
                            card.x = 0; card.y = 0
                    }
                }
                if value.translation.width < -100 {
                    // 延迟时间和动画时长对齐即可
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                        onDelete()
                    }
                }
                */
            }
        ).cornerRadius(8)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

修改完成后左滑卡片触发阈值时,会先执行滑出动画,再从数据源中移除对应卡片,父视图检测到@State数组变更会自动刷新渲染,移除对应的卡片视图。

内容的提问来源于stack exchange,提问作者Sachu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 12:27:02