Как реализовать отслеживание момента, когда палец отпущен от скролла, с использованием SwiftUI?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Отслеживание окончания скролла в 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. Поэтому вышеуказанные подходы остаются основными для решения этой задачи.