2

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.

Khawer K
  • 105
  • 1
  • 6

2 Answers2

0

ScrollViewReader is the solution you're looking for. You may need to play around with the value. Also you'll need to add the .id(0) modifier to your textview.

ScrollView {
    ScrollViewReader { reader in 
        Button("Go to first then anchor trailing.") {
                    value.scrollTo(0, anchor: .trailing)
                }
      // The rest of your code .......
xTwisteDx
  • 2,152
  • 1
  • 9
  • 25
  • Thanks. I am already using `ScrollViewReader`, as in my last code block. I have used a string `id`, which works just as well as an integer, in my experience. The objective, as noted in the question, is not to have to use a button to trigger scrolling. The approach suggested by @asperi seems the right way to go. I am facing one issue with it though which I have noted in a comment on that answer. – Khawer K Nov 14 '21 at 10:19
0

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)
            }
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks. This does work but there is one issue. When text is added for the first time and goes past the screen area, it does not scroll. However, once I scroll the text manually, then scrolling starts to happen automatically for any subsequent additions/deletions. Is there something I am missing? – Khawer K Nov 14 '21 at 10:07