SwiftUI中List内动态设置ID时Hero动画失效问题
SwiftUI动态
matchedGeometryEffect ID的Hero动画修复 问题描述
使用matchedGeometryEffect实现Hero动画时,动态绑定selectedItemInRowIndex作为ID时动画未按预期工作,但硬编码固定ID(如3)时仅对应行动画正常。
问题原因
- 所有列表行的绿色视图始终绑定各自的
item.id作为matchedGeometryEffect的ID,当Overlay中的视图使用选中ID时,SwiftUI无法精准匹配唯一对应的源视图,导致动画混乱。 - List上的全局
.animation修饰符会干扰matchedGeometryEffect的精细动画上下文,破坏动画同步。
修复方案
- 条件化绑定ID:列表行中的绿色视图仅在当前item被选中时,使用
item.id作为共享ID;未选中时使用唯一的非共享ID,避免冲突。 - 移除全局动画:删除List上的全局动画修饰符,完全通过
withAnimation包裹状态变更来控制动画。 - 优化Overlay交互:将黑色背景设置为全屏,确保点击任意区域可关闭详情。
修改后的完整代码
import SwiftUI struct HeroAnimationTest: View { let items: [Item] = [.init(id: 1), .init(id: 2), .init(id: 3), .init(id: 4)] @State var selectedItemInRowIndex: Int? = nil @Namespace var namespace var body: some View { List { ForEach(items, id: \.id) { item in ItemListRow(namespace: namespace, item: item, selectedId: selectedItemInRowIndex) { tappedItem in withAnimation(.spring()) { selectedItemInRowIndex = tappedItem.id } } } .listRowSeparator(.hidden) .listRowBackground(Color.clear) .listRowInsets(EdgeInsets(h: 16, v: 8)) } .scrollIndicators(.never) .listStyle(.plain) .overlay { if let selectedId = selectedItemInRowIndex { largeGreen(selectedId: selectedId) } } } @ViewBuilder func largeGreen(selectedId: Int) -> some View { ZStack { Color.black .ignoresSafeArea() .onTapGesture { withAnimation(.spring()) { selectedItemInRowIndex = nil } } Color.green .frame(width: 200, height: 400) .matchedGeometryEffect(id: selectedId, in: namespace) Text("ID -> \(selectedId)") .foregroundColor(.white) .font(.headline) } } } struct HeroAnimationTest_Previews: PreviewProvider { static var previews: some View { HeroAnimationTest() } } struct Item { let id: Int } struct ItemListRow: View { let namespace: Namespace.ID let item: Item let selectedId: Int? let onGreenTap: (Item) -> Void var body: some View { HStack { Text("ID -> \(item.id)") Color.green .frame(width: 100, height: 40) .matchedGeometryEffect( id: selectedId == item.id ? item.id : "unselected-\(item.id)", in: namespace ) .onTapGesture { onGreenTap(item) } Color.yellow .frame(width: 100, height: 40) } } }
效果说明
修改后,点击任意列表行的绿色视图时,该视图会平滑过渡到Overlay中的大尺寸绿色视图;点击黑色背景时,大视图会动画回到原列表行位置,所有行均能正常触发Hero动画。
内容的提问来源于stack exchange,提问作者rdev
相关产品推荐
相关产品推荐

