← Назад к вопросам

Как реализовать отслеживание момента, когда палец отпущен от скролла, с использованием SwiftUI?

2.3 Middle🔥 112 комментариев
#SwiftUI

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Отслеживание окончания скролла в SwiftUI

В SwiftUI отслеживание момента, когда пользователь отпускает палец после скролла, требует комбинации нескольких подходов, поскольку стандартные компоненты не предоставляют прямого события окончания скролла. Решение зависит от типа скролл-контейнера (ScrollView, List, UITableView внутри UIViewRepresentable).

Основные подходы

1. Использование DragGesture на ScrollView

Это наиболее прямой метод для отслеживания начала и окончания drag-действий:

import SwiftUI

struct ScrollViewWithDragTracking: View {
    @State private var isDragging = false
    
    var body: some View {
        ScrollView {
            VStack {
                ForEach(0..<50, id: \.self) { index in
                    Text("Item \(index)")
                        .padding()
                }
            }
        }
        .simultaneousGesture(
            DragGesture(minimumDistance: 0)
                .onChanged { _ in
                    if !isDragging {
                        print("Начало скролла")
                        isDragging = true
                    }
                }
                .onEnded { _ in
                    print("Окончание скролла")
                    isDragging = false
                }
        )
    }
}

Примечание: minimumDistance: 0 позволяет захватывать даже минимальные движения. Использование .simultaneousGesture важно, чтобы gesture не блокировал скролл.

2. Комбинация UIScrollViewDelegate через UIViewRepresentable

Для более точного контроля (например, определения момента, когда скролл полностью остановился) можно использовать UIKit интеграцию:

import SwiftUI

struct CustomScrollView: UIViewRepresentable {
    class Coordinator: NSObject, UIScrollViewDelegate {
        var onScrollEnded: () -> Void
        
        init(onScrollEnded: @escaping () -> Void) {
            self.onScrollEnded = onScrollEnded
        }
        
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
            if !decelerate {
                // Скролл остановился сразу без замедления
                onScrollEnded()
            }
        }
        
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            // Скролл завершил замедление
            onScrollEnded()
        }
    }
    
    let onScrollEnded: () -> Void
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        scrollView.delegate = context.coordinator
        let contentView = UIView()
        // Добавьте ваш контент здесь
        scrollView.addSubview(contentView)
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(onScrollEnded: onScrollEnded)
    }
}

3. Отслеживание через ScrollViewReader и геометрию

Можно отслеживать изменения позиции скролла и определять моменты, когда движение прекращается:

struct ScrollViewWithPositionTracking: View {
    @State private var scrollOffset: CGFloat = 0
    @State private var previousOffset: CGFloat = 0
    @State private var timer: Timer?
    
    var body: some View {
        ScrollView {
            VStack {
                GeometryReader { geometry in
                    Color.clear
                        .preference(key: ScrollOffsetKey.self, 
                                    value: geometry.frame(in: .global).minY)
                }
                .frame(height: 0)
                
                ForEach(0..<100, id: \.self) { index in
                    Text("Row \(index)")
                        .padding()
                }
            }
        }
        .onPreferenceChange(ScrollOffsetKey.self) { value in
            handleOffsetChange(value)
        }
    }
    
    private func handleOffsetChange(_ newOffset: CGFloat) {
        previousOffset = scrollOffset
        scrollOffset = newOffset
        
        // Таймер для определения остановки скролла
        timer?.invalidate()
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
            if abs(scrollOffset - previousOffset) < 0.1 {
                print("Скролл остановился")
            }
        }
    }
}

struct ScrollOffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

Ключевые рекомендации

  • Для простых случаев используйте DragGesture с .simultaneousGesture.
  • Если требуется точное поведение как в UIKit (например, для LazyVStack с большим количеством элементов), используйте UIViewRepresentable с UIScrollViewDelegate.
  • Метод с GeometryReader и таймером может быть менее точным, но работает без интеграции с UIKit.
  • Учитывайте, что List в SwiftUI имеет внутреннюю оптимизацию и может не реагировать на некоторые gesture-методы так же, как ScrollView.

Важное ограничение: В iOS 16+ появились более совершенные методы отслеживания скролла через scrollPosition и ScrollView modifiers, но они не предоставляют прямого события окончания drag. Поэтому вышеуказанные подходы остаются основными для решения этой задачи.

Как реализовать отслеживание момента, когда палец отпущен от скролла, с использованием SwiftUI? | PrepBro