SwiftUI中使用matchedGeometryEffect时平移旋转视图异常问题
解决SwiftUI卡牌平移与旋转动画不同步问题
问题根源在于你当前的实现中,侧边位置的卡牌视图初始就带有固定的旋转角度,当card.dealt变为true时,该视图直接以旋转后的状态出现,旋转过程没有参与到动画中;而平移动画是由matchedGeometryEffect驱动的视图位置过渡,两者自然无法同步。
修改方案
通过新增状态变量控制旋转角度,让旋转变化也纳入动画闭包的驱动范围,确保平移与旋转同步执行:
import SwiftUI struct Card: Identifiable { var id: String { return value } let value: String var dealt: Bool = false } struct CardView: View { let card: Card var flipped: Bool = false var body: some View { ZStack { Color.white Text("\(card.value)") .padding(4) Color.red.opacity(flipped ? 0.0 : 1.0) } .border(.black, width: 2) } } struct CardsTableView: View { @Namespace private var dealingNamespace @State var card = Card(value: "1", dealt: false) // 新增状态变量控制旋转角度 @State private var rotationDegrees = 0.0 var body: some View { ZStack { Rectangle() .foregroundColor(.clear) .border(.black, width: 2) .padding(10) VStack { ZStack { // 中间位置的卡牌 if !card.dealt { CardView(card: card) .frame(width: 40, height: 70) .matchedGeometryEffect(id: card.id, in: dealingNamespace) .rotationEffect(.degrees(rotationDegrees)) } // 侧边位置的卡牌 if card.dealt { CardView(card: card) .frame(width: 40, height: 70) .matchedGeometryEffect(id: card.id, in: dealingNamespace) .rotationEffect(.degrees(rotationDegrees)) .padding(.trailing, 20) } } Spacer() Button { withAnimation(.linear(duration: 1)) { card.dealt.toggle() // 根据卡牌状态同步更新旋转角度 rotationDegrees = card.dealt ? -90 : 0 } } label: { Text("Deal") } .padding() } } } }
关键修改点
- 新增旋转状态变量:用
@State private var rotationDegrees存储当前旋转角度,让旋转变化可被动画驱动。 - 同步旋转与状态切换:在
withAnimation闭包中,切换card.dealt的同时更新rotationDegrees,确保旋转和平移的动画触发时机完全一致。 - 统一应用旋转效果:给中间和侧边的卡牌视图都添加
rotationEffect,让旋转动画贯穿整个过渡过程。
替代简化方案(无需双视图)
如果你不需要保留matchedGeometryEffect,可以直接用单个视图结合offset和rotationEffect实现同步动画,代码更简洁:
struct CardsTableView: View { @State var card = Card(value: "1", dealt: false) var body: some View { ZStack { Rectangle() .foregroundColor(.clear) .border(.black, width: 2) .padding(10) VStack { ZStack { CardView(card: card) .frame(width: 40, height: 70) .rotationEffect(.degrees(card.dealt ? -90 : 0)) // 根据屏幕宽度计算侧边偏移量,可自行调整数值 .offset(x: card.dealt ? UIScreen.main.bounds.width/2 - 60 : 0) } Spacer() Button { withAnimation(.linear(duration: 1)) { card.dealt.toggle() } } label: { Text("Deal") } .padding() } } } }
内容的提问来源于stack exchange,提问作者rai212
相关产品推荐
相关产品推荐

