skip to Main Content

How can I handle
scrollViewDidEndDecelerating and scrollViewDidEndDragging function in swiftUI

I want to set the position of the scrollView so that the selectedIndex is in the middle when the scrollView stops moving.

struct ContentView: View {
    
    @State private var imageList: [String] = ["Onur","Onur","Onur","Onur","Onur","Onur","Onur","Onur","Onur"]
    private var spacing: CGFloat = UIScreen.main.bounds.width / 2 - 100
    @State private var moffsetX: CGFloat = 0
    @State private var oldFffsetX: CGFloat = 0
    @State private var isScrolling: Bool = false
    @State private var selectedIndex: Int = 0
    
    func horizontalScrollView() -> some View {
        ScrollView(.horizontal){
            Spacer()
            HStack(spacing: spacing){
                Spacer()
                    .frame(width: 50)
                ForEach(imageList, id:.self){ image in
                    Image(image)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 100,height: 100)
                        .clipShape(Circle())
                        .overlay(Circle().stroke(.blue,lineWidth: 4))
                }
                
                Spacer()
                    .frame(width: 50)
            }
            
            .overlay(GeometryReader{ geometry in
                Color.clear.onChange(of: geometry.frame(in: .global).minX) { value, newValue in
                    oldFffsetX = value
                    moffsetX = newValue
                    let index = Int(round(geometry.frame(in: .global).minX / (-1 * (100 + spacing))))
                    if index != selectedIndex{
                        if index < 0{
                            selectedIndex = 0
                        } else if index > imageList.count - 1{
                            selectedIndex = imageList.count - 1
                        } else{
                            selectedIndex = index
                        }
                    }
                    isScrolling = true
                }
            })
            
            Spacer()
        }
        
        
    }
}

2

Answers


  1. You can consider using the CurrentValueSubject with the AnyPublisher to detect the scroll events. Here’s the demonstration of the use case,

    struct ScrollDetectorView: View {
    let scrollSubject: CurrentValueSubject<CGFloat, Never>
    let publisher: AnyPublisher<CGFloat, Never>
    
    init() {
        let scrollSubject = CurrentValueSubject<CGFloat, Never>(0)
        self.publisher = scrollSubject
            .debounce(for: .seconds(0.3), scheduler: DispatchQueue.main)
            .dropFirst()
            .eraseToAnyPublisher()
        self.scrollSubject = scrollSubject
    }
    
    var body: some View {
        ScrollView {
            VStack(spacing: 5) {
                ForEach(0...200, id: .self) { i in
                    Text("(i)")
                        .frame(width: 200, height: 100)
                        .background(Color.green)
                }
            }
            .onPreferenceChange(ViewOffsetKey.self) { scrollSubject.send($0) }
        }.coordinateSpace(name: "scroll")
        .onReceive(publisher) {
            print("Scroll did end: ($0)")
        }
    }
    }
    struct ViewOffsetKey: PreferenceKey {
        typealias Value = CGFloat
        static var defaultValue = CGFloat.zero
        static func reduce(value: inout Value, nextValue: () -> Value) {
            value += nextValue()
        }
    }
    
    Login or Signup to reply.
  2. Add this PreferenceKey to track the vertical scroll offset of a View:

    struct VerticalScrollOffsetKey: PreferenceKey {
        static var defaultValue = CGFloat.zero
        
        static func reduce(value: inout Value, nextValue: () -> Value) {
            value += nextValue()
        }
    }
    

    Add this ViewModifier to allow tracking of a View’s vertical offset and call a scrollPostionUpdate closure when scrolling has stopped:

    extension View {
        
        func onScrollEnded(in coordinateSpace: CoordinateSpace, onScrollEnded: @escaping (CGFloat) -> Void) -> some View {
            modifier(OnVerticalScrollEnded(coordinateSpace: coordinateSpace, scrollPostionUpdate: onScrollEnded))
        }
    }
    
    struct OnVerticalScrollEnded: ViewModifier {
        let coordinateSpace: CoordinateSpace
        let scrollPostionUpdate: (CGFloat) -> Void
        private let scrollViewVerticalOffset = CurrentValueSubject<CGFloat, Never>(0)
        
        func body(content: Content) -> some View {
            content
                .background(
                    GeometryReader(content: { geometry in
                        Color.clear.preference(key: VerticalScrollOffsetKey.self, value: abs(geometry.frame(in: coordinateSpace).origin.y))
                    })
                )
                .onPreferenceChange(VerticalScrollOffsetKey.self) { scrollViewVerticalOffset.send($0) }
                .onReceive(scrollViewVerticalOffset.debounce(for: 0.1, scheduler: DispatchQueue.main).dropFirst(), perform: scrollPostionUpdate)
        }
    }
    

    Usage: Add the .onScrollEnded modifier to the content of the ScrollView and give the ScrollView a coordinateSpace name:

    struct ScrollingEndedView: View {
        private let coordinateSpaceName = "scrollingEndedView_coordinateSpace"
        
        var body: some View {
            ScrollView {
                VStack {
                    ForEach(0...100, id: .self) { rowNum in
                        Text("Row (rowNum)")
                            .frame(maxWidth: .infinity)
                            .padding(.vertical)
                            .background(Color.orange)
                    }
                }
                .onScrollEnded(in: .named(coordinateSpaceName), onScrollEnded: updateScrollPosition(_:))
            }
            .coordinateSpace(name: coordinateSpaceName) // add the coordinateSpaceName to the ScrollView itself
        }
        
        private func updateScrollPosition(_ position: CGFloat) {
            print("scrolling ended @: (position)")
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search