SwiftUI Z-Stack按钮触发卡片滑动时层级异常问题求助
问题描述
实现了类似Tinder的卡片左右滑动功能,支持手指滑动与按钮触发滑动操作。手指滑动时功能完全正常,但通过按钮触发滑动时,卡片会从Z-Stack的下一层被移除,而非手指滑动时那样从顶层移除。
相关代码
可滑动卡片组件
@EnvironmentObject var buttonState: ButtonState @State var offset: CGFloat = 0 @GestureState var isDragging: Bool = false @State var endSwipe: Bool = false @State var buttonPressed = false GeometryReader { proxy in let size = proxy.size let index = CGFloat(cardVM.getIndex(card: card)) Card() .frame(width: size.width, height: size.height) .offset(x: offset) .rotationEffect(.init(degrees: getRotation(angle: 8))) .contentShape(Rectangle().trim(from: 0, to: endSwipe ? 0 : 1)) .gesture( DragGesture() .updating($isDragging, body: { value, out, _ in out = true }) .onChanged({ value in let translation = value.translation.width offset = (isDragging ? translation : .zero) self.swipeRight = translation > 0 ? true : false buttonState.right = translation > 0 ? true : false buttonState.isDragging = true }) .onEnded({ value in let width = getRect().width - 50 let translation = value.translation.width let checkingStatus = (translation > 0 ? translation : -translation) buttonState.isDragging = false withAnimation { if checkingStatus > (width / 2) { offset = (translation > 0 ? width : -width) * 2 endSwipe = true translation > 0 ? rightSwipe() : leftSwipe() endSwipeActions() } else { offset = .zero } } }) ) .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ACTIONFROMBUTTON"), object: nil)) { data in guard let info = data.userInfo else { return } let id = info["id"] as? String ?? "" let rightSwipe = info["rightSwipe"] as? Bool ?? false let width = getRect().width - 50 if card.id == id { withAnimation { offset = (rightSwipe ? width : -width) * 2 endSwipeActions() if rightSwipe { self.rightSwipe() } else { leftSwipe() } } } } }
ButtonState类
class ButtonState: ObservableObject { @Published var right = false @Published var isDragging = false }
主视图右滑触发代码
func doSwipe(rightSwipe: Bool = false) { guard let first = cardVM.displayingCards?.first else { return } NotificationCenter.default.post(name: NSNotification.Name("ACTIONFROMBUTTON"), object: nil, userInfo: [ "id": first.id, "rightSwipe": rightSwipe ]) } Button("Swipe right") { buttonState.isDragging = true buttonState.right = true doSwipe(rightSwipe: true) }
问题原因
按钮触发滑动时,同步调用了卡片移除方法(rightSwipe()/leftSwipe()),导致卡片在动画完成前就从数据源中被移除,Z-Stack的下层卡片提前显示,视觉上呈现为从下层被移除。同时,按钮触发逻辑中未设置endSwipe = true,和手指滑动的状态逻辑不一致,可能导致contentShape的裁剪规则未生效,进一步影响层级显示效果。
解决方案
1. 对齐状态设置逻辑
在按钮触发的onReceive回调中添加endSwipe = true,和手指滑动的状态保持一致:
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ACTIONFROMBUTTON"), object: nil)) { data in guard let info = data.userInfo else { return } let id = info["id"] as? String ?? "" let rightSwipe = info["rightSwipe"] as? Bool ?? false let width = getRect().width - 50 if card.id == id { withAnimation { offset = (rightSwipe ? width : -width) * 2 endSwipe = true // 补全状态设置 endSwipeActions() if rightSwipe { self.rightSwipe() } else { leftSwipe() } } } }
2. 延迟执行卡片移除操作
利用iOS 15+支持的withAnimation完成回调,等滑动动画结束后再移除卡片,避免动画未完成就切换层级:
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ACTIONFROMBUTTON"), object: nil)) { data in guard let info = data.userInfo else { return } let id = info["id"] as? String ?? "" let rightSwipe = info["rightSwipe"] as? Bool ?? false let width = getRect().width - 50 if card.id == id { withAnimation { offset = (rightSwipe ? width : -width) * 2 endSwipe = true endSwipeActions() } completion: { // 动画完成后再执行移除逻辑 if rightSwipe { self.rightSwipe() } else { leftSwipe() } } } }
3. 优化触发逻辑(可选)
如果NotificationCenter存在状态同步问题,可以改用ViewModel驱动的方式:给CardViewModel添加triggerSwipe(for cardId: String, rightSwipe: Bool)方法,卡片组件监听ViewModel的状态变化来执行滑动操作,减少跨组件通知的延迟问题。
验证
修改后,按钮触发滑动时,卡片会先完成顶层的滑动动画,动画结束后再从数据源中移除,视觉效果和手指滑动完全一致。
内容的提问来源于stack exchange,提问作者MaxPayne777
相关产品推荐
相关产品推荐

