I am writing a SwiftUI iOS app where I need a Text
view to automatically scroll to the end of its content whenever the content is updated. The update happens from the model. To not complicate this question with the details of my app, I have created a simple scenario where I have two text fields and a text label. Any text entered in the text fields is concatenated and shown in the text label. The text label is enclosed in a horizontal ScrollView
and can be scrolled manually if the text is longer than the screen width. What I want to achieve is for the text to scroll to the end automatically whenever the label is updated.
Here is the simple model code:
class Model: ObservableObject {
var firstString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
var secondString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
@Published var combinedString = ""
}
This is the ContentView:
struct ContentView: View {
@ObservedObject var model: Model
var body: some View {
VStack(alignment: .leading, spacing: 10) {
TextField("First string: ", text: $model.firstString)
TextField("Second string: ", text: $model.secondString)
Spacer().frame(height: 20)
Text("Combined string:")
ScrollView(.horizontal) {
Text(model.combinedString)
}
}
}
}
From the research I have done, the only way I have found to scroll to the end of the text, without having to do it manually, is to add a button to the view, which causes the text in the label to scroll to the end.
Here is the above ScrollView
embedded in a ScrollViewReader
, with a button to effect the scrolling action.
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
Button("Scroll to end") {
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
.padding()
.foregroundColor(.white)
.background(Color.black)
}
}
This works, provided the intention is to use a button to effect the scrolling action.
My question is: Can the scrolling action above be triggered whenever the model is updated, without the need to click a button.
Any help or pointers will be much appreciated.
Thanks.
I assume you wanted this:
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
.onChange(of: model.combinedString) { // << here !!
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
}
}