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

SwiftUI中LazyVGrid/LazyVStack的scrollTo偏移异常问题求助

SwiftUI LazyVGrid/LazyVStack scrollTo 滚动偏移问题解决

问题描述

在SwiftUI中使用LazyVGrid或LazyVStack展示长产品列表时,点击顶部分类按钮调用scrollTo方法滚动至对应分类顶部,出现随机偏移,无法准确定位到分类标题位置。

复现代码

基础代码结构

import SwiftUI

struct Product: Identifiable {
    let id = UUID()
    let name: String
    let price: Double
    let imageName: String
}

struct Category: Identifiable {
    let id = UUID()
    let name: String
    let products: [Product]
}

class MockData {
    static func generate() -> [Category] {
        var categories = [Category]()
        for i in 1...30 {
            let productCount = Int.random(in: 5...50)
            var products = [Product]()
            for j in 1...productCount {
                products.append(Product(name: "Product \(j)", price: Double.random(in: 10...100), imageName: "photo"))
            }
            categories.append(Category(name: "Category \(i)", products: products))
        }
        return categories
    }
}

struct ContentView: View {
    let categories = MockData.generate()
    
    @State private var selectedCategoryIndex: Int? = nil
    
    var body: some View {
        NavigationView {
            ScrollViewReader { scrollView in
                VStack {
                    ScrollView(.horizontal) {
                        HStack {
                            ForEach(categories.indices, id: \.self) { index in
                                Button(action: {
                                    withAnimation {
                                        scrollView.scrollTo(categories[index].id, anchor: .top)
                                    }
                                }) {
                                    Text(categories[index].name)
                                        .padding()
                                        .background(Color.gray.opacity(0.2))
                                        .cornerRadius(8)
                                }
                            }
                        }
                        .padding()
                    }
                    
                    // LazyVGrid 原代码(存在问题)
                    ScrollView {
                        LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) {
                            ForEach(categories) { category in
                                Section(header: Text(category.name).font(.headline).padding()) {
                                    ForEach(category.products) { product in
                                        VStack {
                                            Image(systemName: product.imageName)
                                                .resizable()
                                                .frame(width: 120, height: 120)
                                            Text(product.name)
                                            Text("$\(product.price, specifier: "%.2f")")
                                                .font(.subheadline)
                                                .foregroundColor(.gray)
                                        }
                                        .padding()
                                        .background(Color.white)
                                        .cornerRadius(8)
                                        .shadow(radius: 2)
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

LazyVStack 问题复现代码

ScrollView {
    LazyVStack {
        ForEach(categories.indices, id: \.self) { index in
            Section(header: Text(categories[index].name)
                .font(.headline)
                .padding()
                .id(categories[index].id)) {
                    ForEach(categories[index].products) { product in
                        HStack {
                            Image(systemName: "photo")
                                .resizable()
                                .frame(width: 50, height: 50)
                                .padding()
                            VStack(alignment: .leading) {
                                Text(product.name)
                                Text(String(format: "$%.2f", product.price))
                            }
                            Spacer()
                        }
                        .background(Color.white)
                        .cornerRadius(10)
                        .shadow(radius: 2)
                        .padding(.horizontal)
                    }
                }
        }
    }
    .padding(.bottom, 50)
}

解决办法

核心问题在于scrollTo的目标ID绑定位置错误,以及Lazy容器的布局特性导致位置计算偏差,可通过以下步骤修复:

1. 绑定ID到分类整体容器

移除默认的Section结构,改用VStack包裹分类标题和产品列表,将id绑定到整个分类容器上,确保scrollTo定位的是分类的最顶部位置。

修改后的 LazyVGrid 代码

ScrollView {
    LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) {
        ForEach(categories) { category in
            // 用VStack包裹分类标题和产品列表,绑定ID到容器
            VStack(alignment: .leading, spacing: 0) {
                Text(category.name)
                    .font(.headline)
                    .padding()
                
                ForEach(category.products) { product in
                    VStack {
                        Image(systemName: product.imageName)
                            .resizable()
                            .frame(width: 120, height: 120)
                        Text(product.name)
                        Text("$\(product.price, specifier: "%.2f")")
                            .font(.subheadline)
                            .foregroundColor(.gray)
                    }
                    .padding()
                    .background(Color.white)
                    .cornerRadius(8)
                    .shadow(radius: 2)
                }
            }
            .id(category.id) // 关键:ID绑定到整个分类容器
        }
    }
}

修改后的 LazyVStack 代码

ScrollView {
    LazyVStack(spacing: 0) {
        ForEach(categories) { category in
            VStack(alignment: .leading, spacing: 0) {
                Text(category.name)
                    .font(.headline)
                    .padding()
                
                ForEach(category.products) { product in
                    HStack {
                        Image(systemName: "photo")
                            .resizable()
                            .frame(width: 50, height: 50)
                            .padding()
                        VStack(alignment: .leading) {
                            Text(product.name)
                            Text(String(format: "$%.2f", product.price))
                        }
                        Spacer()
                    }
                    .background(Color.white)
                    .cornerRadius(10)
                    .shadow(radius: 2)
                    .padding(.horizontal)
                }
            }
            .id(category.id) // 关键:ID绑定到整个分类容器
        }
    }
    .padding(.bottom, 50)
}

2. 优化scrollTo执行时机

动画可能干扰Lazy容器的位置计算,可移除withAnimation或延迟执行scrollTo:

Button(action: {
    // 移除动画,或用延迟确保布局稳定
    DispatchQueue.main.async {
        scrollView.scrollTo(categories[index].id, anchor: .top)
    }
}) {
    Text(categories[index].name)
        .padding()
        .background(Color.gray.opacity(0.2))
        .cornerRadius(8)
}

3. 处理导航栏影响(可选)

如果使用NavigationView,可添加edgesIgnoringSafeArea(.top)或调整容器内边距,避免导航栏遮挡滚动目标:

ScrollView {
    // ... 内容 ...
}
.edgesIgnoringSafeArea(.top)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 09:20:59