SwiftUI:不使用ForEach遍历闪卡ZStack实现类Tinder滑动效果
SwiftUI 实现 Tinder 式滑动卡片(非批量加载)
核心思路
不用ForEach一次性渲染所有卡片,通过状态变量跟踪当前显示的卡片索引,仅渲染当前卡片,滑动完成后自动切换到下一张,同时通过手势和动画实现流畅的滑动交互。
完整实现代码
import SwiftUI // 自定义卡片模型(可根据实际需求调整) struct Flashcard: Identifiable { let id = UUID() let front: String let back: String } class Model: ObservableObject { @Published var flashcards: [Flashcard] = [ Flashcard(front: "卡片1正面", back: "卡片1背面"), Flashcard(front: "卡片2正面", back: "卡片2背面"), Flashcard(front: "卡片3正面", back: "卡片3背面") ] } // 单个卡片视图 struct AllCardView: View { let flashcard: Flashcard var body: some View { RoundedRectangle(cornerRadius: 20) .fill(Color.white) .aspectRatio(CGSize(width: 150, height: 300), contentMode: .fit) .shadow(radius: 5) .overlay( VStack { Text(flashcard.front) .font(.title) .bold() Spacer() Text(flashcard.back) .font(.subheadline) } .padding() ) } } struct ContentView: View { @EnvironmentObject var model: Model @State private var currentIndex = 0 @State private var dragOffset = CGSize.zero @State private var isCardRemoved = false var body: some View { VStack { ZStack { // 背景占位视图 RoundedRectangle(cornerRadius: 20) .aspectRatio(CGSize(width: 150, height: 300), contentMode: .fit) .foregroundColor(.gray.opacity(0.2)) .shadow(radius: 5) .padding(50) // 仅渲染当前卡片 if currentIndex < model.flashcards.count { AllCardView(flashcard: model.flashcards[currentIndex]) .padding() .offset(dragOffset) .rotationEffect(.degrees(Double(dragOffset.width / 10))) .gesture( DragGesture() .onChanged { value in dragOffset = value.translation } .onEnded { value in // 设置滑动触发阈值 let threshold: CGFloat = 100 if abs(value.translation.width) > threshold { // 执行卡片滑出动画 withAnimation(.easeOut(duration: 0.3)) { dragOffset = CGSize(width: value.translation.width > 0 ? 500 : -500, height: value.translation.height) isCardRemoved = true } // 动画完成后切换到下一张卡片 DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { currentIndex += 1 dragOffset = .zero isCardRemoved = false } } else { // 滑动距离不足,回弹到原位 withAnimation(.easeOut) { dragOffset = .zero } } } ) .zIndex(isCardRemoved ? 0 : 1) // 滑出时降低层级,避免遮挡后续交互 } else { // 所有卡片滑完的提示 Text("所有卡片已完成") .font(.title) .foregroundColor(.gray) } } } } } // 预览视图 struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environmentObject(Model()) } }
关键部分说明
状态变量控制:
currentIndex:跟踪当前显示的卡片索引,状态更新时SwiftUI会自动重新渲染视图,切换到下一张卡片。dragOffset:记录拖动时的偏移量,实现卡片跟随手指移动的效果。isCardRemoved:标记卡片是否正在滑出,控制z轴层级避免遮挡后续操作。
手势处理逻辑:
onChanged:实时更新偏移量,让卡片跟随手指移动。onEnded:判断滑动距离是否超过阈值,超过则执行滑出动画,完成后自动更新索引;未超过则回弹到原位。
非批量加载:
不再用ForEach遍历整个数组,仅根据currentIndex渲染当前卡片,避免一次性加载大量卡片占用资源。动画增强:
添加rotationEffect让卡片拖动时产生轻微旋转,模拟真实的卡片滑动手感;用withAnimation包裹状态变化,实现平滑的过渡动画。
内容的提问来源于stack exchange,提问作者Maria Kravchenko
相关产品推荐
相关产品推荐

