LazyHStack嵌套ScrollView中点击项滚动动画卡顿问题求助
解决嵌套LazyHStack的ScrollView平滑滚动与展开动画同步问题
问题描述
使用嵌套LazyHStack的ScrollView时,点击项的展开动画与滚动到视图内的操作不同步,部分可见的项无法同时完成展开和滚动,动画卡顿明显,尤其是项处于左右边缘部分可见状态时。期望实现选中项平滑展开、两侧项同步移位的连贯动画效果。
修复后的完整代码
import SwiftUI struct CarouselItem: Identifiable, Equatable { private(set) var id: UUID = .init() var title: String var year: String var runtime: String var certification: String var posterImage: String var stillImage: String } struct CarousalItemView: View { let item: CarouselItem @Binding var selectedItem: CarouselItem? @Namespace private var namespace private var expanded: Bool { item.id == selectedItem?.id } var body: some View { VStack(alignment: .leading) { ZStack { Image(item.posterImage) .resizable() .scaledToFill() .scaleEffect(expanded ? 1.5 : 1, anchor: .topLeading) .frame(width: expanded ? 250 : 150, height: 200) .matchedGeometryEffect(id: "image_\(item.id)", in: namespace, anchor: .topLeading) .opacity(expanded ? 0 : 1) Image(item.stillImage) .resizable() .scaledToFill() .frame(width: expanded ? 250 : 150, height: 200) .matchedGeometryEffect(id: "image_\(item.id)", in: namespace, anchor: .topLeading) .opacity(expanded ? 1 : 0) .overlay(alignment: .leading) { if expanded { itemDetailsView() } } } .clipShape(RoundedRectangle(cornerRadius: 10)) Text(item.title) .frame(height: 50) } } @ViewBuilder func itemDetailsView() -> some View { VStack(alignment: .leading, spacing: 15) { Spacer() Text(item.title) .font(.title2) HStack { Text("CBFC: " + item.certification) .padding(.horizontal, 5) .overlay { Rectangle() .stroke(.white, lineWidth: 1) } Text(item.runtime) Text(item.year) } .shadow(color: .gray, radius: 5, y: 5) Button { } label: { Text("Trailer") } .buttonStyle(.borderedProminent) .tint(.gray) .padding(.bottom, 15) } .padding(.horizontal, 10) } } struct ContentView: View { var items: [CarouselItem] = [ .init(title: "Oppenheimer", year: "2023", runtime: "3h 1m", certification: "UA", posterImage: "poster1", stillImage: "still1"), .init(title: "Barbie", year: "2023", runtime: "1h 54m", certification: "PG", posterImage: "poster2", stillImage: "still2"), .init(title: "The Martian", year: "2015", runtime: "2h 24m", certification: "UA", posterImage: "poster3", stillImage: "still3"), .init(title: "The Shawshank Redemption", year: "1994", runtime: "2h 22m", certification: "U", posterImage: "poster4", stillImage: "still4"), .init(title: "The Godfather", year: "1972", runtime: "2h 55m", certification: "UA", posterImage: "poster5", stillImage: "still5"), .init(title: "The Dark Knight", year: "2008", runtime: "2h 32m", certification: "UA", posterImage: "poster6", stillImage: "still6") ] @State var selectedItem: CarouselItem? var body: some View { VStack { HStack { Text("Popular films") .font(.title2.bold()) Spacer() } ScrollViewReader { proxy in ScrollView(.horizontal) { LazyHStack(spacing: 20) { ForEach(items, id: \.id) { item in CarousalItemView(item: item, selectedItem: $selectedItem) .frame(width: selectedItem?.id != item.id ? 150 : 250, height: 250) .clipShape(RoundedRectangle(cornerRadius: 10)) .contentShape(Rectangle()) .id(item.id) .onTapGesture { withAnimation(.easeInOut(duration: 0.3)) { if selectedItem?.id == item.id { selectedItem = nil } else { selectedItem = item proxy.scrollTo(item.id, anchor: .center) } } } } } } .frame(height: 250) .scrollIndicators(.hidden) .clipped() } Spacer() } .padding() .background { Color(.darkGray).ignoresSafeArea() } .foregroundStyle(.white) } }
核心改动说明
- 统一动画控制:移除视图内所有独立的
.animation修饰符,将所有动画逻辑集中到onTapGesture的withAnimation块中,避免多动画触发冲突,保证动画同步性 - 修正滚动时机:确保选中状态更新后立即执行滚动操作,避免因状态延迟导致的滚动与展开不同步
- 优化几何匹配:给
matchedGeometryEffect的ID添加item唯一标识(image_\(item.id)),避免不同项之间的几何干扰,保证每个项的动画独立准确 - 移除冗余代码:去掉不必要的
DispatchQueue.main.async,onTapGesture默认在主队列执行,简化逻辑 - 显式设置ID:为每个列表项显式添加
.id(item.id),确保ScrollViewReader能精准定位到目标项
内容的提问来源于stack exchange,提问作者batman
相关产品推荐
相关产品推荐

