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
相关产品推荐
相关产品推荐

