SwiftUI ScrollView 手势识别器

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

如何检测 ScrollView 何时被拖动?

在我的 ScrollView 中,我有一个

@Binding scrollPositionOffset
变量,我用
.onChange(of:)
观察该变量,然后使用
ScrollViewReader.scrollTo()
以编程方式滚动到该位置。这很好用,但是当我直接滚动 ScrollView 时,我还需要更新
scrollPositionOffset
。我正在努力做到这一点,因为这会触发
.onChange(of:)
关闭并进入循环。

我的解决方案是仅当我将

ScrollViewReader.scrollTo()
变量设置为 false 时才有条件地调用
localScrolling
。我尝试使用
DragGesture.onChanged
.onEnded
进行设置,但这与导致滚动的拖动手势不同,因此
.onEnded
永远不会触发。

我认为我需要的是 ScrollView 的 @GestureRecognizer 类似于 UIScrollView 的

isDragging
isTracking
(我知道我可以使用 UIScrollView,但我不知道如何使用,这似乎可能需要更多工作! !我会接受一个告诉我如何将其放入 SwiftUIView 的答案)

上下文(如果有人对我的实际场景有更清晰的解决方案):

我有一个 ScrollView,我正在以编程方式滚动它以创建类似于 Xcode 中的 Minimap 视图的效果(即,我有一个与 ScrollView 相邻的缩小视图,拖动小地图会导致 ScrollView 滚动)。

当我使用小地图时,这非常有用,但我很难实现相反的情况:移动 ScrollView 的位置来更新小地图视图。

代码


@Binding var scrollPositionOffset: CGFloat
let zoomMultiplier:CGFloat = 1.5

 var body: some View{
        
        ScrollViewReader { scrollViewProxy in
            GeometryReader{ geometry in
                ScrollView {
                    ZStack(alignment:.top){

         //The content of my ScrollView

                    MagnifierView()
                        .frame(height: geometry.size.height * zoomMultiplier)
                    
         //I'm using this as my offset reference

                        Rectangle()
                            .frame(height:10)
                            .alignmentGuide(.top) { _ in
                                geometry.size.height * zoomMultiplier * -scrollPositionOffset
                            }
                            .id("scrollOffset")    
                    }
                }
                .onAppear(){
                    scrollViewProxy.scrollTo("scrollOffset", anchor: .top)
                }
                
                .onChange(of: scrollPositionOffset, perform: { _ in
            
        //Only call .scrollTo() if the view isn't already being scrolled by the user

                    if !localScrolling {
                    scrollViewProxy.scrollTo("scrollOffset", anchor: .top)
                    }
                    
                })
                
                .gesture(
                    DragGesture()
                        .onChanged{gesture in
                            localScrolling = true
                            
                            let offset = gesture.location.y/(zoomMultiplier * geometry.size.height)

                            scrollPositionOffset = offset
                        }
        
                        .onEnded({gesture in

     //Doesn't ever fire when scrolling

                            localScrolling = false
                        })
                )
            }
        }
    }

ios swift swiftui gesture-recognition swiftui-scrollview
1个回答
8
投票

使用

ScrollViewStyle

struct CustomScrollView: ScrollViewStyle {
    @Binding var isDragging: Bool
    func make(body: AnyView, context: Context) -> some View {
        body
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }
    class Coordinator: ScrollViewCoordinator {
        var parent: CustomScrollView
        init(parent: CustomScrollView) {
            self.parent = parent
        }
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
            parent.isDragging = false
        }
        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
            parent.isDragging = true
        }
    }
}

struct TestView: View {
    @State var isDragging = false
    var body: some View {
        ScrollView {
            
        }.scrollViewStyle(CustomScrollView(isDragging: $isDragging))
    }
}

2.0.0版本更新:

你现在要做的就是:

  1. 添加一个属性来跟踪
    ScrollView
    的状态
    @ScrollState var state
    
  2. 使用
    scrollViewStyle
    修饰符
    .scrollViewStyle(.defaultStyle($state))
    

测试视图:

struct TestView: View {
    @ScrollState var state
    var body: some View {
        ScrollView {
            ...
        }.scrollViewStyle(.defaultStyle($state))
        .onChange(of: state.isDragging) { newValue in
            print(newValue)
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.