如何让ScrollView始终滚动到底部

5

我有一个高度固定的ScrollView,其中包含一个文本(Text()),该文本不断更新自我的viewModel。 如果文本过长需要滚动才能看到文本的结尾,我希望它能自动滚动,以便我始终看到文本的结尾。

这可能吗?

  ScrollView {
            Text(vm.text)
               .frame(minWidth: 20, alignment: .leading)

             }
             .frame(height: 200)

注意:以下是我问题的简化版。在我的应用程序中,有时文本不会被更新,并且需要滚动。
我尝试使用scrollViewReader … 类似于以下内容:
 ScrollView {
                ScrollViewReader() { proxy in
                    Text(vm.text)
                        .frame(minWidth: 20, alignment: .leading)
                    Text("").id(0)
                }
            }
            .frame(height: 200)

我考虑了滚动到空文本的想法,但是我无法弄清如何触发

withAnimation {
                proxy.scrollTo(0)
            }

我看到的所有示例都使用按钮来触发,但我需要在文本更新时触发。


点击这里:"虽然不是完美的解决方案,但我注意到对scrollTo函数进行速率限制解决了这个问题。一旦我更新了,比如说,每5次,它就会像应该的那样滚动,而不需要先手动滚动。" - undefined
2个回答

2

您可以使用.onChange来响应vm.text的更改,然后滚动到末尾。

请注意,ScrollView应该在ScrollViewReader内部!
原则上,它应该像这样工作:

struct ContentView: View {
    
    @State private var vmtext = "Test Text"
    
    // timer change of text for testing
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                Text(vmtext)
                    .id(0)
            }
            // react on change of text, scroll to end
            .onChange(of: vmtext) { newValue in
                proxy.scrollTo(0, anchor: .bottom)
            }
        }
        .frame(width: 100, height: 200, alignment: .leading)
        .border(.primary)
        .padding()
        
        // timer change of text for testing
        .onReceive(timer) { _ in
            vmtext += " added new text"
        }
   }
}

我在这里遇到的问题是,只有在滚动视图手动滚动一次后才能正常工作。

有没有解决需要手动滚动scrollview的方法? - Oded Ben Dov

0

我通过@ChrisR的代码进行了调整,加入了一个hack,至少可以展示如何让scrollTo工作。它在两个几乎完全相同但id不同(其中一个有一点padding)的视图之间切换。

import SwiftUI

struct ContentView: View {
@State private var vmtext = "\n\n\n\nTest Text"
@State private var number = 0
@State private var id = 0
// timer change of text for testing
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()

var body: some View {
    ScrollViewReader { proxy in
        ScrollView {
            id == 0 ?
            VStack {
                Text(vmtext)
                    .padding(.top, 0)
                    .font(.title2)
                    .id(0)
            }
            :
            VStack {
                Text(vmtext)
                    .padding(.top, 1)
                    .font(.title2)
                    .id(1)
            }
        }
        // react on change of text, scroll to end
        .onChange(of: vmtext) { newValue in
            print("onChange entered. id: \(id)")
            proxy.scrollTo(id, anchor: .bottom)
        }
    }
    .frame(height: 90, alignment: .center)
    .frame(maxWidth: .infinity)
    .border(.primary)
    .padding()
    // timer change of text for testing
    .onReceive(timer) { _ in
        if id == 0 { id = 1} else {id = 0}
        number += 1
        vmtext += " word\(number)"
    }
  }
}

注意: 如果显示的文本视图的高度没有改变,则proxy.scrollTo将被忽略。将填充设置为相同,hack就会失效。

从var vmtext中删除"\n\n\n\n"会破坏hack。它们使文本视图的初始大小大于滚动视图窗口,因此立即可滚动-或者什么都可以:-)。如果您删除它们,则在用手指进行初始滚动后,滚动将开始工作。

编辑:

这是一个没有填充和"\n\n\n\n" hack的版本,它使用双旋转hack

import SwiftUI

struct ContentView: View {
@State private var vmtext = "Test Text"
@State private var number = 0
@State private var id = 0
// timer change of text for testing
let timer = Timer.publish(every: 0.3, on: .main, in: .common).autoconnect()

var body: some View {
    ScrollViewReader { proxy in
        ScrollView {
            Group {
                id == 0 ?
                VStack {
                    Text(vmtext)
                        .font(.title2)
                        .id(0)
                }
                :
                VStack {
                    Text(vmtext)
                        .font(.title2)
                        .id(1)
                }
            }
            .padding()
            .rotationEffect(Angle(degrees: 180))
        }
        .rotationEffect(Angle(degrees: 180))
        // react on change of text, scroll to end
        .onChange(of: vmtext) { newValue in
            withAnimation {
                if id == 0 { id = 1 } else { id = 0 }
                proxy.scrollTo(id, anchor: .bottom)
            }
        }
    }
    .frame(height: 180, alignment: .center)
    .frame(maxWidth: .infinity)
    .border(.primary)
    .padding()
    // timer change of text for testing
    .onReceive(timer) { _ in
        number += 1
        vmtext += " word\(number)"
    }
  }
 }

如果文本从视图顶部开始,并且只有在文本填满视图时才滚动,就像SwiftUI TextEditor一样,那将是很好的...但是withAnimation不起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接