我在 ScrollView 中有水平进度条,我需要在用户滚动时更改该进度条值。
有没有办法将一些值绑定到当前滚动位置?
你可以用几个
GeometryReader
s来做到这一点。
我的方法:
ScrollView
容器的总高度PreferenceKey
s设置proportion
@State
值代码:
struct ContentView: View {
@State private var scrollViewHeight: CGFloat = 0
@State private var proportion: CGFloat = 0
var body: some View {
VStack {
ScrollView {
VStack {
ForEach(0 ..< 100) { index in
Text("Item: \(index + 1)")
}
}
.frame(maxWidth: .infinity)
.background(
GeometryReader { geo in
let scrollLength = geo.size.height - scrollViewHeight
let rawProportion = -geo.frame(in: .named("scroll")).minY / scrollLength
let proportion = min(max(rawProportion, 0), 1)
Color.clear
.preference(
key: ScrollProportion.self,
value: proportion
)
.onPreferenceChange(ScrollProportion.self) { proportion in
self.proportion = proportion
}
}
)
}
.background(
GeometryReader { geo in
Color.clear.onAppear {
scrollViewHeight = geo.size.height
}
}
)
.coordinateSpace(name: "scroll")
ProgressView(value: proportion, total: 1)
.padding(.horizontal)
}
}
}
struct ScrollProportion: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
结果:
如果你想让 George 的代码可重用,你可以通过创建 2 个修改器并将它们应用于你想要动画的视图来实现。这是解决方案:
ContentView.swift:
struct ContentView: View {
@State private var scrollViewHeight: CGFloat = 0
@State private var proportion: CGFloat = 0
@State private var proportionName: String = "scroll"
var body: some View {
VStack {
ScrollView {
VStack {
ForEach(0 ..< 100) { index in
Text("Item: \(index + 1)")
}
}
.frame(maxWidth: .infinity)
.modifier(
ScrollReadVStackModifier(
scrollViewHeight: $scrollViewHeight,
proportion: $proportion,
proportionName: proportionName
)
)
}
.modifier(
ScrollReadScrollViewModifier(
scrollViewHeight: $scrollViewHeight,
proportionName: proportionName
)
)
ProgressView(value: proportion, total: 1)
.padding(.horizontal)
}
}
}
ScrollViewAnimation.swift
struct ScrollReadVStackModifier: ViewModifier {
@Binding var scrollViewHeight: CGFloat
@Binding var proportion: CGFloat
var proportionName: String
func body(content: Content) -> some View {
content
.background(
GeometryReader { geo in
let scrollLength = geo.size.height - scrollViewHeight
let rawProportion = -geo.frame(in: .named(proportionName)).minY / scrollLength
let proportion = min(max(rawProportion, 0), 1)
Color.clear
.preference(
key: ScrollProportion.self,
value: proportion
)
.onPreferenceChange(ScrollProportion.self) { proportion in
self.proportion = proportion
}
}
)
}
}
struct ScrollReadScrollViewModifier: ViewModifier {
@Binding var scrollViewHeight: CGFloat
var proportionName: String
func body(content: Content) -> some View {
content
.background(
GeometryReader { geo in
Color.clear.onAppear {
scrollViewHeight = geo.size.height
}
}
)
.coordinateSpace(name: proportionName)
}
}
struct ScrollProportion: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}