SwiftUI中.matchedGeometryEffect与ForEach配合失效问题
问题原因与解决方案
问题根源
你遇到的堆叠问题是因为所有列表项使用了相同的固定ID(如rectangleId、titleId)作为matchedGeometryEffect的标识符。SwiftUI通过ID来关联不同状态下的视图,相同ID会让系统认为所有列表项是同一个视图,从而将它们叠加在同一位置。
修复方案
为每个列表项的几何效果ID添加唯一标识后缀(即Item的id),确保每个视图拥有独立的几何跟踪ID。修改代码如下:
修正后的完整代码
import SwiftUI struct Item: Identifiable { let id = UUID().uuidString let title: String let subtitle: String let image: ImageResource } struct ContentView: View { @Namespace var sheet private var rectangleId = "Rectangle" private var titleId = "Title" private var subtitleId = "Subtitle" @State var isExpanded: Bool = false @State private var selected: Item? let items: [Item] = [ .init(title: "Test 1", subtitle: "testing stuff 1", image: .test), .init(title: "Test 2", subtitle: "testing stuff 2", image: .test1), .init(title: "Test 3", subtitle: "testing stuff 3", image: .test2), .init(title: "Test 4", subtitle: "testing stuff 4", image: .test3), .init(title: "Test 5", subtitle: "testing stuff 5", image: .test4), ] var body: some View { NavigationStack { ZStack { if isExpanded, let selectedItem = selected { sheetView(item: selectedItem) } else { ScrollView { ForEach(items, id: \.id) { item in normalView(item: item) } } .padding() } } } } @ViewBuilder func normalView(item: Item) -> some View { Image(item.image) .resizable() .scaledToFit() .clipShape(RoundedRectangle(cornerRadius: 12)) // 为ID添加Item唯一标识后缀 .matchedGeometryEffect(id: "\(rectangleId)-\(item.id)", in: sheet) .onTapGesture { withAnimation { selected = item isExpanded.toggle() } } .overlay(alignment: .bottomLeading) { VStack(alignment: .leading) { Text(item.title) .font(.title) .matchedGeometryEffect(id: "\(titleId)-\(item.id)", in: sheet) Text(item.subtitle) .matchedGeometryEffect(id: "\(subtitleId)-\(item.id)", in: sheet) } .foregroundStyle(.white) .padding() } } @ViewBuilder func sheetView(item: Item) -> some View { ScrollView { Image(item.image) .resizable() .scaledToFit() // 与选中项的ID保持一致 .matchedGeometryEffect(id: "\(rectangleId)-\(item.id)", in: sheet) .overlay(alignment: .bottomLeading) { VStack(alignment: .leading) { Text(item.title) .font(.title) .matchedGeometryEffect(id: "\(titleId)-\(item.id)", in: sheet) Text(item.subtitle) .matchedGeometryEffect(id: "\(subtitleId)-\(item.id)", in: sheet) } .foregroundStyle(.white) .padding() } ForEach(0..<50) { index in Text("New test text lol \(index)") } } .ignoresSafeArea(edges: .top) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { withAnimation { isExpanded.toggle() } } label: { Image(systemName: "xmark.circle.fill") } } } } } #Preview { ContentView() }
关键修改点
- 几何ID唯一化:将原固定ID(如
rectangleId)与Item的id拼接,形成"\(rectangleId)-\(item.id)"这样的唯一标识 - 可选值安全处理:在
sheetView的显示条件中添加let selectedItem = selected,避免强制解包崩溃 - 工具栏按钮优化:将加号图标改为关闭图标(
xmark.circle.fill),符合弹窗关闭的交互逻辑
内容的提问来源于stack exchange,提问作者bennyyy999
相关产品推荐
相关产品推荐

