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
相关产品推荐
相关产品推荐

