SwiftUI:如何根据滚动视图的用户当前滚动位置同步/更改进度条进度?

问题描述 投票:0回答:2

我在 ScrollView 中有水平进度条,我需要在用户滚动时更改该进度条值。

有没有办法将一些值绑定到当前滚动位置?

swiftui scrollview
2个回答
2
投票

你可以用几个

GeometryReader
s来做到这一点。

我的方法:

  1. 获取
    ScrollView
    容器的总高度
  2. 获取内容的内部高度
  3. 找出总可滚动高度的差异
  4. 获取滚动视图容器顶部和内容顶部之间的距离
  5. 将顶部之间的距离除以总的可滚动高度
  6. 使用
    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()
    }
}

结果:


0
投票

如果你想让 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()
    }
    
}
© www.soinside.com 2019 - 2024. All rights reserved.