Library / Drag, swipe & scroll
Textured drag (velocity-driven rumble)
Dragging across a surface that should feel textured — the faster the finger moves, the stronger the rumble.
import SwiftUI
import CoreHaptics
struct TexturedSurface: View {
@State private var engine: CHHapticEngine?
@State private var player: CHHapticAdvancedPatternPlayer?
var body: some View {
RoundedRectangle(cornerRadius: 20)
.fill(.gray.opacity(0.3))
.frame(height: 220)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
if player == nil { startLoop() }
// Map velocity (pts/s) to 0–1
let speed = min(1, abs(value.velocity.width) / 2000
+ abs(value.velocity.height) / 2000)
update(intensity: Float(0.2 + 0.8 * speed),
sharpness: Float(0.3 * speed))
}
.onEnded { _ in stopLoop() }
)
.onAppear {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics
else { return }
engine = try? CHHapticEngine()
try? engine?.start()
}
}
private func startLoop() {
guard let engine else { return }
let event = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2),
],
relativeTime: 0, duration: 100) // long-lived; we stop it manually
guard let pattern = try? CHHapticPattern(events: [event], parameters: [])
else { return }
player = try? engine.makeAdvancedPlayer(with: pattern)
try? player?.start(atTime: CHHapticTimeImmediate)
}
private func update(intensity: Float, sharpness: Float) {
try? player?.sendParameters([
CHHapticDynamicParameter(parameterID: .hapticIntensityControl,
value: intensity, relativeTime: 0),
CHHapticDynamicParameter(parameterID: .hapticSharpnessControl,
value: sharpness, relativeTime: 0),
], atTime: CHHapticTimeImmediate)
}
private func stopLoop() {
try? player?.stop(atTime: CHHapticTimeImmediate)
player = nil
}
}