I have a ScrollView inside a VStack, that will be filled and refreshed each time a new array item appearing. This array containing two sections (qa.question and qa.answer) and was created in a function outside the view. The ScrollView will be filled with these array items as text. I want to automatically scroll down to the bottom, each time a new array will be displayed.
The ScrollView looks like:
ScrollView(showsIndicators: false) {
ForEach(requ.questionAndAnswers) { qa in
VStack(spacing: 2) {
Text(qa.question)
.bold()
.foregroundColor(colorScheme == .dark ? .white : .black)
.frame(minWidth: 600, maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
Text(qa.answer)
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding([.bottom], 10)
.frame(minWidth: 600, maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.id(requ.questionAndAnswers.count) // give each answer a unique id
}
}
}.padding([.top, .leading, .trailing], 50)
To find the last item for scrolling, i give Text(qa.answer) the id of array.count. With "print()" i can see, the counter increase each time a new array item appears. Now i embed the whole ScrollView into a ScrollViewReader and try to perform a scroll to the bottom (the requ.questionAndAnswer.count) :
ScrollViewReader { scrollView in
ScrollView(showIndicators: false) {
....
}.padding([.top, .leading, .trailing], 50)
.onChange(of: requ.questionAndAnswers.count) { _ in
withAnimation {
proxy.scrollTo(requ.questionAndAnswers.count - 1)
}
}
}
But nothing happened. I tried different version (.onChange, onAppear), make the performing action asynchronous with "DispatchQueue.main.async", or to giving time with "DispatchQueue.main.asyncAfter". No chance.
Any advice would be predicated.
Try this approach, to ... automatically scroll down to the bottom, each time a new array will be displayed
. The example code "attach" the .id(qa.id)
to the VStack
.
When a new element is added to the array (using the test button),
the ScrollViewReader
proxy is scrolled to the last item, based the qa.id
.
// for testing
class Questioner: ObservableObject {
// 50 QA
@Published var questionAndAnswers = Array(repeating: QA(question: "q1", answer: "a1"), count: 50)
}
// for testing
struct QA: Identifiable {
let id = UUID()
var question: String
var answer: String
}
struct ContentView: View {
@StateObject var requ = Questioner() // for testing
var body: some View {
VStack {
// for testing, adding one more QA
Button("add one"){
requ.questionAndAnswers.append(QA(question: "q-last", answer: "a-last"))
}.buttonStyle(.bordered)
ScrollViewReader { proxy in // <-- here proxy not scrollView
ScrollView(showsIndicators: false) {
ForEach(requ.questionAndAnswers) { qa in
VStack(spacing: 12) {
Text(qa.question).foregroundColor(.blue).bold()
Text(qa.answer).foregroundColor(.red)
Divider()
}.id(qa.id) // <-- here
}
}.padding([.top, .leading, .trailing], 50)
.onChange(of: requ.questionAndAnswers.count) { _ in
if let last = requ.questionAndAnswers.last {
withAnimation {
proxy.scrollTo(last.id) // <-- here
}
}
}
}
}
}
}