提问人:eugene_prg 提问时间:2/19/2021 更新时间:3/3/2023 访问量:874
SwiftUI:如何根据用户当前滚动视图的滚动位置同步/更改进度条进度?
SwiftUI: How do I sync/change progress bar progress based on scrollview’s user current scroll position?
答:
3赞
George
10/6/2021
#1
你可以用几秒来做到这一点。GeometryReader
我的方法:
- 获取集装箱的总高度
ScrollView
- 获取内容的内在高度
- 求总可滚动高度的差值
- 获取滚动视图容器顶部与内容顶部之间的距离
- 将顶部之间的距离除以可滚动的总高度
- 使用 s 设置值
PreferenceKey
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()
}
}
结果:
评论
0赞
Iraklis Eleftheriadis
3/3/2023
您将如何使此代码可重用到许多视图中?
0赞
George
3/3/2023
@IraklisEleftheriadis 您可能只是将一些视图内容传递给视图,并使用它而不是VStack { ForEach ... }
0赞
Iraklis Eleftheriadis
3/3/2023
我最终为 VStack 和 ScrollView 创建了两个自定义修改器。我在修改器中添加了您作为@Binding提供的两个变量,以便我可以使用它们来更新主视图中的进度条。
0赞
Iraklis Eleftheriadis
3/3/2023
#2
如果你想使 George 的代码可重用,你可以通过创建 2 个修饰符并将它们应用于你想要动画的视图来实现。解决方案如下:
内容视图.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()
}
}
评论