ScrollViewReader是SwiftUI中的一个视图容器,它可以协调多个滚动视图的滚动位置。
再开始今天的主题之前,我们先用ScrollerView来做一个简单的例子。
struct ScrollerViewReaderSample: View {
var body: some View {
ScrollView {
ForEach(0..<50) { i in
Text("This is #(i)")
.frame(height: 180)
.frame(maxWidth: .infinity)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 10)
.padding()
}
}
}
}
循环了50个Text的 ScrollerView,此时如果你想要定位到第30个Text的位置,你该如何做? 你如果只使用ScrollerView肯定是很难做到的,此时你只需要和ScrollViewReader配合使用,你讲轻松拿捏这个需求。
下面具体来看看。
我们添加如下代码把Text的大列表包裹起来
ScrollView {
ScrollViewReader { proxy in
Button("Scroll to 30") {
proxy.scrollTo(30)
}
ForEach(0..<50) { i in
Text("This is #(i)")
.frame(height: 180)
.frame(maxWidth: .infinity)
.background()
.cornerRadius(10)
.shadow(radius: 10)
.padding()
}
}
}
ScrollViewProxy 用于控制一个ScrollView的滚动 位置和行为。
当你点击Button,让ScrollView滚动到第30个Text的位置时,他不会变化。原因是因为你们有把Text和proxy建立关系,当你指定要滚动到第30个Text时,它不知道在哪里。
所以我们需要建立联系,我们需要给Text设置一个id。
Text("This is #(i)")
.frame(height: 180)
.frame(maxWidth: .infinity)
.background()
.cornerRadius(10)
.shadow(radius: 10)
.padding()
.id(i) // id的值是foreach的索引值 i
这时当你点击按钮,就可以跳转到指定的位置了
跳转方法有两个参数,一个参数是位置,另一个是锚点位置。定义如下:
public func scrollTo<ID>(_
id: ID,
anchor: UnitPoint? = nil // 指名跳转停留的位置
) where ID : Hashable
proxy.scrollTo(30, anchor: .bottom) // 停留在第三十个Text的 底部
proxy.scrollTo(30, anchor: .top) // 停留在第三十个Text的 顶部
使用proxy.scrollTo方法ScrollerView滚动是没有动画的。如果你想要有动
效果,可以自己在调用的地方加上动画效果。
Button("Scroll to 30") {
withAnimation(.spring()) {
proxy.scrollTo(30, anchor: .bottom)
}
}
如果我们不想嵌套在ScrollerView里面,改如何实现代码呢?比如我们输入一个数字,就让它跳转到对应的位置?
那么下面我们来改造一下,增加一个TextField,当你输入一个数字,然后 点击Button,就跳转到输入的数字的位置。
struct ScrollViewReaderSample: View {
@State var textFiledText: String = ""
@State var scrollToIndex: Int = 0
var body: some View {
VStack {
VStack {
TextField("Input a number", text: $textFiledText)
.font(.largeTitle)
.keyboardType(.numberPad)
.frame(height: 55)
.frame(maxWidth: .infinity)
.padding()
.padding(.horizontal)
.background(Color.green)
Text("Click me".uppercased())
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.black.cornerRadius(10))
.onTapGesture {
if let index = Int(textFiledText) {
scrollToIndex = index
}
}
}
ScrollView {
ScrollViewReader { proxy in
ForEach(0..<50) { i in
Text("This is #(i)")
.frame(height: 180)
.frame(maxWidth: .infinity)
.background()
.cornerRadius(10)
.shadow(radius: 10)
.padding()
.id(i)
}
.onChange(of: scrollToIndex) { newValue in
withAnimation(.spring()) {
proxy.scrollTo(newValue)
}
}
}
}
}
}
}
当我们点击按钮,就可以跳转到输入框里面输入的数字的位置。我们主要使用了onChange这个方法,那么官方的定义是当指定值更改时执行操作。在我们的例子中, 也就是当scrollToIndex 值改变,就会调用后面的方法。官方解释如下:
ScrollViewReader另一个很好的场景就是聊天界面,当有新的消息来了,就滚动到最新消息的视图位置,我们使用数组来关联,指定跳转到数组的大小位置即可。
大家有什么看法呢?欢迎留言讨论。
公众号:RobotPBQ