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

SwiftUI实现列表Section底部或指定视图的自动滚动

解决SwiftUI List编辑状态自动滚动问题

先别急着换ScrollView+LazyVStack,试试List原生可行方案

你之前嵌套ScrollView的做法会和List内部的UIScrollView冲突,导致scrollTo失效。正确的做法是用ScrollViewReader包裹List,直接操作List的滚动代理,具体步骤如下:

  1. 给需要滚动到的目标视图(比如你的SaveCancelButtonView)设置唯一ID,比给Section设置ID更精准
  2. 进入编辑状态后,延迟调用scrollTo(因为SwiftUI视图更新需要时间,立刻调用可能找不到目标视图)
  3. 指定anchor: .bottom确保目标视图底部显示在可视区域

示例代码:

struct ContentView: View {
    @State private var sections: [SectionModel] = [
        SectionModel(title: "基础设置", selectedOption: "选项1", options: ["选项1", "选项2", "选项3"]),
        SectionModel(title: "高级设置", selectedOption: "A", options: ["A", "B", "C", "D"])
    ]
    
    var body: some View {
        ScrollViewReader { proxy in
            List {
                ForEach($sections) { $section in
                    Section {
                        if section.isEditing {
                            // 滚轮选择器
                            Picker("选择项", selection: $section.selectedOption) {
                                ForEach(section.options, id: \.self) { option in
                                    Text(option).tag(option)
                                }
                            }
                            .pickerStyle(.wheel)
                            
                            // 保存取消按钮,设置唯一ID
                            SaveCancelButtonView(
                                onSave: { section.isEditing = false },
                                onCancel: { section.isEditing = false }
                            )
                            .id("\(section.id)-saveBtn")
                        } else {
                            Text(section.title)
                                .onTapGesture {
                                    section.isEditing = true
                                    // 延迟滚动,确保视图已渲染完成
                                    DispatchQueue.main.async {
                                        proxy.scrollTo("\(section.id)-saveBtn", anchor: .bottom)
                                    }
                                }
                        }
                    } header: {
                        Text(section.title)
                    }
                }
            }
        }
    }
}

// 示例数据模型
struct SectionModel: Identifiable {
    let id = UUID().uuidString
    var title: String
    var isEditing = false
    var selectedOption: String
    var options: [String]
}

// 你的保存取消按钮视图
struct SaveCancelButtonView: View {
    let onSave: () -> Void
    let onCancel: () -> Void
    
    var body: some View {
        HStack {
            Button("取消") { onCancel() }
            Spacer()
            Button("保存") { onSave() }
        }
        .padding()
    }
}

如果List方案仍有问题,再考虑切换到ScrollView+LazyVStack

如果List的内部布局限制导致滚动行为不稳定,改用ScrollView+LazyVStack会更灵活,完全由你控制布局和滚动逻辑,适合复杂的编辑场景。这种方式需要你手动模拟List的Section样式,但新手也能快速上手:

示例代码:

struct ContentView: View {
    @State private var sections: [SectionModel] = [
        SectionModel(title: "基础设置", selectedOption: "选项1", options: ["选项1", "选项2", "选项3"]),
        SectionModel(title: "高级设置", selectedOption: "A", options: ["A", "B", "C", "D"])
    ]
    
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                LazyVStack(spacing: 12) {
                    ForEach($sections) { $section in
                        // 模拟List Section的头部和内容布局
                        VStack(spacing: 0) {
                            Text(section.title)
                                .font(.headline)
                                .padding(.vertical, 8)
                                .padding(.horizontal)
                                .background(Color(.systemGroupedBackground))
                            
                            VStack {
                                if section.isEditing {
                                    Picker("选择项", selection: $section.selectedOption) {
                                        ForEach(section.options, id: \.self) { option in
                                            Text(option).tag(option)
                                        }
                                    }
                                    .pickerStyle(.wheel)
                                    
                                    SaveCancelButtonView(
                                        onSave: { section.isEditing = false },
                                        onCancel: { section.isEditing = false }
                                    )
                                    .id("\(section.id)-saveBtn")
                                } else {
                                    Text(section.title)
                                        .padding()
                                        .onTapGesture {
                                            section.isEditing = true
                                            DispatchQueue.main.async {
                                                proxy.scrollTo("\(section.id)-saveBtn", anchor: .bottom)
                                            }
                                        }
                                }
                            }
                            .background(Color(.systemBackground))
                        }
                        .cornerRadius(8)
                        .padding(.horizontal)
                    }
                }
                .padding(.top, 8)
            }
        }
    }
}

总结

  • 优先尝试ScrollViewReader+List的方案,改动最小,贴近你现有代码结构
  • 如果List的滚动行为不符合预期,再切换到ScrollView+LazyVStack,虽然需要重构布局,但能完全控制滚动和样式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 14:06:13