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

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()
            }
        }
    }
}

关键修改点

  1. 新增旋转状态变量:用@State private var rotationDegrees存储当前旋转角度,让旋转变化可被动画驱动。
  2. 同步旋转与状态切换:在withAnimation闭包中,切换card.dealt的同时更新rotationDegrees,确保旋转和平移的动画触发时机完全一致。
  3. 统一应用旋转效果:给中间和侧边的卡牌视图都添加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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 00:54:29