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

SwiftUI动态列表中ScrollViewProxy.scrollTo失效问题排查与解决

问题原因分析
  1. 数据源ID频繁变更
    当前使用计算属性convertedCharacters,每次userInput变化时都会通过map重新生成所有RowItem实例,每个新实例的UUID都是全新的。调用scrollTo时,新生成的最后一项还未被List完成渲染,ScrollViewProxy无法匹配到对应的ID(此时List仍在使用旧视图层级)。

  2. 滚动触发时机过早
    onReceive(Just(userInput))会在userInput变化的瞬间触发,此时List数据源刚更新,但视图还未完成重建与布局,新列表项还未被加入ScrollView的可滚动容器,导致scrollTo找不到目标。

解决办法

方案1:维护稳定数据源(推荐)

放弃计算属性,改用@State数组存储列表项,每次输入仅追加新项,避免重建整个列表,保证每个项的ID稳定:

import Combine
import SwiftUI

struct RowItem: Equatable, Hashable, Identifiable {
    let id = UUID()
    let value: String
}

struct ContentView: View {
    @State var userInput = ""
    @State private var items: [RowItem] = []

    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                List(items) { i in
                    Text("Character: \(i.value)")
                }
                TextField("Enter a string", text: $userInput)
                    .onChange(of: userInput) { newValue in
                        // 提取新增字符,避免重复添加
                        let newChars = newValue.suffix(from: items.count)
                        for char in newChars {
                            items.append(RowItem(value: String(char)))
                        }
                        // 等待视图更新完成后滚动
                        DispatchQueue.main.async {
                            if let lastItem = items.last {
                                proxy.scrollTo(lastItem.id)
                            }
                        }
                    }
            }
        }
    }
}

方案2:调整滚动触发时机(适配原有计算属性场景)

若坚持使用计算属性,需确保List完成布局后再执行滚动,可通过DispatchQueue.main.async延迟操作:

import Combine
import SwiftUI

struct RowItem: Equatable, Hashable, Identifiable {
    let id = UUID()
    let value: String
}

struct ContentView: View {
    @State var userInput = ""
 
    var convertedCharacters: [RowItem] {
        userInput.map {
            RowItem(value: String($0))
        }
    }
    
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                List(convertedCharacters) { i in
                    Text("Character: \(i.value)")
                }
                TextField("Enter a string", text: $userInput)
                    .onChange(of: convertedCharacters) { _ in
                        DispatchQueue.main.async {
                            proxy.scrollTo(convertedCharacters.last?.id)
                        }
                    }
            }
        }
    }
}

关键说明

  • 使用@State数组存储时,仅追加新元素,保证RowItem的id唯一且稳定,让ScrollViewProxy能精准定位目标。
  • DispatchQueue.main.async的作用是等待当前视图更新周期完成,确保新列表项已渲染到ScrollView中,再执行滚动操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 19:35:19