Library / Pickers, sliders & steppers
Rotating dial / ratchet
A rotary knob (volume, timer) that clicks per step, with sharper clicks at faster spin — Digital-Crown style.
import SwiftUI
import CoreHaptics
struct RatchetDial: View {
@State private var angle: Angle = .zero
@State private var lastStep = 0
@State private var lastTime = Date()
@State private var engine: CHHapticEngine?
let degreesPerStep = 15.0
var body: some View {
Circle()
.fill(.gray.gradient)
.overlay(alignment: .top) {
Capsule().fill(.white).frame(width: 6, height: 24).padding(8)
}
.frame(width: 180, height: 180)
.rotationEffect(angle)
.gesture(
RotateGesture()
.onChanged { value in
angle = value.rotation
let step = Int(value.rotation.degrees / degreesPerStep)
if step != lastStep {
// Sharper click when spinning fast
let dt = Date().timeIntervalSince(lastTime)
let sharpness = Float(min(1, 0.3 + 0.1 / max(dt, 0.02)))
click(sharpness: sharpness)
lastStep = step
lastTime = Date()
}
}
)
.onAppear {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics
else { return }
engine = try? CHHapticEngine()
try? engine?.start()
}
}
private func click(sharpness: Float) {
guard let engine else { return }
let event = CHHapticEvent(eventType: .hapticTransient, parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness),
], relativeTime: 0)
if let pattern = try? CHHapticPattern(events: [event], parameters: []),
let player = try? engine.makePlayer(with: pattern) {
try? player.start(atTime: CHHapticTimeImmediate)
}
}
}