## Pull-to-refresh threshold

- Use case: The moment a pull gesture crosses the "release to refresh" threshold, plus a success on completion.
- Feel: A rigid click exactly at the threshold — the sensation of a mechanism engaging — then a success pattern when new content lands.
- APIs: UIImpactFeedbackGenerator(style: .rigid), .sensoryFeedback(.impact)
- Minimum OS: iOS 17+ / iOS 13+

### SwiftUI (iOS 17+)

```swift
import SwiftUI

struct FeedView: View {
    @State private var items: [String] = (1...20).map { "Item \($0)" }
    @State private var refreshed = 0

    var body: some View {
        List(items, id: \.self) { Text($0) }
            .refreshable {
                // The system plays the threshold haptic for .refreshable.
                await reload()
                refreshed += 1
            }
            .sensoryFeedback(.success, trigger: refreshed)
    }

    func reload() async {
        try? await Task.sleep(for: .seconds(1))
        items.shuffle()
    }
}
```

### UIKit (custom threshold)

```swift
final class FeedViewController: UITableViewController {
    private let threshold: CGFloat = -110
    private let clickHaptic = UIImpactFeedbackGenerator(style: .rigid)
    private var armed = false

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let pull = scrollView.contentOffset.y + scrollView.adjustedContentInset.top

        if pull < threshold && !armed {
            armed = true
            clickHaptic.impactOccurred()   // mechanism engages
        } else if pull > threshold * 0.6 && armed && !scrollView.isDragging {
            armed = false                  // re-arm after release
        }
    }
}
```

### Notes
- One click per crossing — track an "armed" flag so wobbling around the threshold does not machine-gun the engine.
