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

SwiftUI多TabView下同对象matchedGeometryEffect动画异常求助

SwiftUI TabView中Hero动画图片混淆问题

我正在开发一个App Store模拟项目,尝试实现Hero动画:

  • 首页的matchedGeometryEffect运行正常(理想效果)
  • 但在另一个TabView中,同一张图片以不同样式展示时,SwiftUI会混淆图片,跳转至详情页的过程中无法正确追踪来源(异常效果)
  • 之后我在HomePageCard和SavedPageCard的matchedGeometryEffect中添加了isSource: false,TabView内图片不再混淆,但从详情页返回首页或收藏页时,图片无法正确缩放(部分修复效果)

相关代码

TabBar

struct TabBar: View {
    @Namespace var animation
    @StateObject var detailObject = DetailPageViewModel()
    
    var body: some View {
        ZStack {
            TabView {
                //Home Page
                HomePage(animation: animation)
                    .environmentObject(detailObject)
                    .tabItem({
                        Image(systemName: "house")
                        Text("Home")
                    })
                
                //Saved Page
                SavedPage(animation: animation)
                    .environmentObject(detailObject)
                    .tabItem({
                        Image(systemName: "square.stack.3d.down.right")
                        Text("Saved")
                    })
            }          
            // hiding TabView when show detail page
            .opacity(detailObject.show ? 0 : 1)      
            if detailObject.show {
                DetailPage(detailPageViewModel: detailObject, animation: animation)
            }
        }
    }
}

HomePage

struct HomePage: View {
    
    var animation: Namespace.ID  //For Homepage animation
    @EnvironmentObject var detailPageViewModel : DetailPageViewModel

    var body: some View {
        ScrollView(.vertical, showsIndicators: true){
            Spacer()
            HStack {
                VStack(alignment: .leading) {
                       //Banner info
                }
                .padding(.leading, 15)
                Spacer()
            }
            LazyVStack (alignment: .leading) {
                ForEach (detailPageViewModel.HomepageList){ recipeDets in
                    HomePageCard(recipeDetail: recipeDets, animation: animation)
                        .onTapGesture {
                            withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.8)){
                                detailPageViewModel.selectedRecipe = recipeDets
                                detailPageViewModel.show.toggle()
                            }
                        }
                }
            }
        }
        .onAppear {
            detailPageViewModel.getData()
        }
    }
}

HomePageCard

struct HomePageCard: View {
    var recipeDetail : RecipeDetail
    
    // 获取系统配色方案
    @Environment(\.colorScheme) var color
    
    var animation: Namespace.ID
    
    var body: some View {
        VStack{
            
            LazyImage(url: URL(string: recipeDetail.thumbnailPath))
                .aspectRatio(contentMode: .fill)
                .matchedGeometryEffect(id: recipeDetail.thumbnailPath, in: animation)
                
                
            HStack{
                VStack(alignment: .leading) {
                    Text(recipeDetail.kcals) // 后续需根据设置中的排序条件确定显示内容
                        .font(.headline)
                        .foregroundColor(.secondary)
                    Text(recipeDetail.name)
                        .font(.title)
                        .fontWeight(.black)
                        .foregroundColor(.primary)
                        .lineLimit(3)
                    Text(shortDescriptionGenerator(recipeDetail.description))
                        .foregroundColor(.secondary)
                }
                .layoutPriority(100)
                Spacer()
            }
            .matchedGeometryEffect(id: recipeDetail.id, in: animation)
            .padding()
        }
        .background(color == .dark ? Color(red: 152/255, green: 152/255, blue: 157/255, opacity: 0.1) : Color(red: 243/255, green: 241/255, blue: 241/255, opacity: 1.0))
        .cornerRadius(20)
        .overlay(
            RoundedRectangle(cornerRadius: 20)
                .stroke(Color(.sRGB, red: 150/255, green: 150/255, blue: 150/255, opacity: color == .dark ? 0.6 : 0.2), lineWidth: 1)
        )
        .padding([.top, .horizontal])
    }
}

SavedPage

struct SavedPage: View {
    //@ObservedObject var detailPageViewModel = DetailPageViewModel()
    var animation: Namespace.ID
    @EnvironmentObject var detailPageViewModel: DetailPageViewModel
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: true){
            Spacer()
            HStack {
                VStack(alignment: .leading) {
                    // Banner Info
                }
                .padding(.leading, 15)
                Spacer()
            }
            
            LazyVStack (alignment: .leading) {
                ForEach (detailPageViewModel.HomepageList){ recipeDets in
                    RegularCard(recipeDetail: recipeDets, animation: animation)
                        .onTapGesture {
                            withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.8)){
                                detailPageViewModel.selectedRecipe = recipeDets
                                detailPageViewModel.show.toggle()
                                print(detailPageViewModel.show)
                            }
                        }
                }
            }
        }.onAppear{
            detailPageViewModel.getData()
        }
    }
}

SavedPageCard

struct RegularCard: View {
    var recipeDetail : RecipeDetail
    var animation: Namespace.ID
    @Environment(\.colorScheme) var color

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 20)
                .fill()
                .foregroundColor(color == .dark ? Color(red: 152/255, green: 152/255, blue: 157/255, opacity: 0.2) : Color(red: 243/255, green: 241/255, blue: 241/255, opacity: 1.0))
                .aspectRatio(2.2/1, contentMode: .fit)
            HStack {
                
                LazyImage(url: URL(string: recipeDetail.thumbnailPath))
                    .aspectRatio(contentMode: .fill)
                    .matchedGeometryEffect(id: recipeDetail.thumbnailPath, in: animation)
                    .frame(width: 120, height: 120)
                    .cornerRadius(10.0)
                    .padding([.top, .leading, .bottom])
            
                VStack (alignment: .leading){
                    Text(recipeDetail.name)
                        .font(.system(.title2))
                        .padding(.top)
                    Text(recipeDetail.methodSummary)
                            .padding([.bottom, .trailing])
                            .font(.system(.headline))
                    Spacer()
                    HStack(alignment: .center){
                        Image(systemName: recipeDetail.isLiked ? "hand.thumbsup.fill" : "hand.thumbsup")
                            .imageScale(.small)
                        Text("\(recipeDetail.likes)")
                            .font(.callout)
                        Image(systemName: "flame")
                            .imageScale(.small)
                        Text(recipeDetail.kcals)
                            .font(.callout)
                        Text("\(recipeDetail.ratings, specifier: "%.1f")")
                            .font(.callout)
                        Image(systemName: recipeDetail.isSaved ? "bookmark.fill" : "bookmark")
                            .imageScale(.small)
                    }.layoutPriority(100)
                    Spacer()
                }
                Spacer()
            }
        }
        .padding(.horizontal, 9.0)       
    }
}

DetailPage

struct DetailPage: View {
    @ObservedObject var detailPageViewModel : DetailPageViewModel
    var animation: Namespace.ID
    //var imageType: String
    
    var body: some View {
        ScrollView {
            ZStack {
                
                LazyImage(url: URL(string: detailPageViewModel.selectedRecipe.thumbnailPath), resizingMode: .aspectFill)
                    .aspectRatio(contentMode: .fit)
                    .matchedGeometryEffect(id: detailPageViewModel.selectedRecipe.thumbnailPath, in: animation)
                    .ignoresSafeArea()
                
                VStack{
                    HStack {
                        Spacer()
                        Button(action: {
                            withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.8)){
                                                            detailPageViewModel.show.toggle()
                            }
                        }){
                            Image(systemName: "xmark")
                                .imageScale(.medium)
                                .foregroundColor(Color.black.opacity(0.7))
                                .padding()
                                .background(Color.white.opacity(0.7))
                                .clipShape(Circle())
                        }
                    }.padding()
                    Spacer()   
                }  
            }   
        }
        .ignoresSafeArea(.all, edges: .top)
        .statusBar(hidden: true)
    }
}

内容的提问来源于stack exchange,提问作者GeekNomore

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 07:45:33