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

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()
}

关键修改点

  1. 几何ID唯一化:将原固定ID(如rectangleId)与Item的id拼接,形成"\(rectangleId)-\(item.id)"这样的唯一标识
  2. 可选值安全处理:在sheetView的显示条件中添加let selectedItem = selected,避免强制解包崩溃
  3. 工具栏按钮优化:将加号图标改为关闭图标(xmark.circle.fill),符合弹窗关闭的交互逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 01:01:23