I want to set the scroll position of a ScrollView containing a LazyVStack such that it shows a view nested arbitrarily deeply within that stack.
Currently I give the view which should be scrolled to an id using the .id modifier and then attempt to use a ScrollViewReader to scroll to that view using that id.
However this does not set the scroll position if the target view is not loaded by the LazyVStack and it appears that this approach will only work for views which are direct children of the lazy stack.
Is there any way, that I can achieve the desired behaviour without making the target views direct children of the lazy stack?
The example below displays a list of lists of strings, where the outer list is displayed using a LazyVStack and the inner lists use a VStack.
Pressing the button in ContentView should scroll the ScrollView to the Text view given by the id.
However this currently only works if the ScrollView is scrolled far enough, such that the StringsView containing the target view is loaded by the lazy stack.
import SwiftUI
class StringsViewModel: ObservableObject {
let uuid: UUID = UUID()
@Published var strings: [String] = []
init(_ range: Range<Int>) {
for i in range {
strings.append(String(i))
}
}
}
struct StringsView: View {
@ObservedObject private var viewModel: StringsViewModel
init(viewModel: StringsViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack {
ForEach(viewModel.strings, id: \.self) { str in
Text(str)
.id(Int(str)) // The id to use in order to scroll to that view.
}
}
}
}
class ViewModel: ObservableObject {
@Published var stringsModels: [StringsViewModel] = []
init(blockCount: Int, numsPerBlock: Int) {
for i in 0..<blockCount {
self.stringsModels.append(StringsViewModel((i*numsPerBlock)..<((i+1)*numsPerBlock)))
}
}
}
struct ContentView: View {
@StateObject private var viewModel: ViewModel = ViewModel(blockCount: 5, numsPerBlock: 250)
var body: some View {
ScrollViewReader { proxy in
VStack {
// Pressing this button should set the scroll position of the scroll view.
Button {
proxy.scrollTo(500)
} label: {
Text("Scroll")
}
// The scroll view whose scroll position should be modified by the button.
ScrollView(.vertical) {
LazyVStack {
ForEach(viewModel.stringsModels, id: \.uuid) { stringModel in
StringsView(viewModel: stringModel)
.border(Color.black)
}
}
}
}
}
}
}
#Preview(body: {
ContentView()
})
ObservableObjectlike you do inclass ViewModel: ObservableObjectwith[StringsViewModel]inside. SO has plenty of explanations about this, search for it. Alternatively you could try using the more modern@Observablethat you can nest, see Apple docs Managing model data in your app, requires iOS17+