Files
Reflect/Shared/Services/HapticFeedbackManager.swift
Trey t 45d83cff89 Redesign haptic patterns per animation and play on selection
Rewrite all 10 celebration haptic patterns to match visual timing and
feel distinct: confetti gets playful falling taps, explosion gets a deep
boom, shatter gets glassy cracks, morph gets liquid breathing, etc.
Play the matching haptic when selecting a new vote animation in
customization (respects haptic feedback toggle).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 16:43:29 -05:00

718 lines
28 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// HapticFeedbackManager.swift
// Reflect
//
// Haptic feedback patterns that match celebration animations.
//
import UIKit
import CoreHaptics
@MainActor
final class HapticFeedbackManager {
static let shared = HapticFeedbackManager()
private var engine: CHHapticEngine?
private init() {
prepareEngine()
}
private func prepareEngine() {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
do {
engine = try CHHapticEngine()
engine?.resetHandler = { [weak self] in
try? self?.engine?.start()
}
try engine?.start()
} catch {
engine = nil
}
}
/// Play a haptic pattern that matches the given celebration animation type.
func play(for animationType: CelebrationAnimationType) {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics,
let engine else {
// Fall back to basic haptic on devices without CoreHaptics
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
return
}
do {
let pattern = try hapticPattern(for: animationType)
let player = try engine.makePlayer(with: pattern)
try engine.start()
try player.start(atTime: CHHapticTimeImmediate)
} catch {
// Fallback to simple impact
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
}
}
// MARK: - Pattern Definitions
private func hapticPattern(for animationType: CelebrationAnimationType) throws -> CHHapticPattern {
switch animationType {
case .confettiCannon:
return try confettiPattern()
case .vortexCheckmark:
return try vortexPattern()
case .explosionReveal:
return try explosionPattern()
case .flipReveal:
return try flipPattern()
case .shatterReform:
return try shatterPattern()
case .pulseWave:
return try pulseWavePattern()
case .fireworks:
return try fireworksPattern()
case .morphBlob:
return try morphPattern()
case .zoomTunnel:
return try tunnelPattern()
case .gravityDrop:
return try gravityPattern()
}
}
// Confetti: poppy burst upward checkmark spring light scattered taps falling like confetti
// Visual: 0.0 launch + glow | 0.3 checkmark spring | 0.5 confetti falling | 1.3 fade
private func confettiPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Cannon pop sharp, bright, like a party popper
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.0
))
// Checkmark spring-in at 0.3s
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.7),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5)
],
relativeTime: 0.3
))
// Confetti pieces falling light, playful, irregular taps with varying sharpness
let confettiTimes: [Double] = [0.55, 0.65, 0.72, 0.82, 0.93, 1.05, 1.15, 1.25, 1.35, 1.45]
let sharpnesses: [Float] = [0.9, 0.5, 0.8, 0.3, 0.7, 0.4, 0.6, 0.3, 0.5, 0.2]
for (i, time) in confettiTimes.enumerated() {
let intensity = Float(0.4 - Double(i) * 0.03)
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: max(intensity, 0.1)),
CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpnesses[i])
],
relativeTime: time
))
}
return try CHHapticPattern(events: events, parameters: [])
}
// Vortex: spirals spin 00.5s ring snaps at 0.3s checkmark pops 0.5s settles 0.7s
// Character: whirring buildup with accelerating ticks, then a tight satisfying snap
private func vortexPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Spinning wind-up accelerating ticks like a roulette wheel slowing in reverse
let tickTimes: [Double] = [0.0, 0.07, 0.13, 0.18, 0.22, 0.25, 0.27, 0.29, 0.30]
for (i, time) in tickTimes.enumerated() {
let progress = Float(i) / Float(tickTimes.count)
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.15 + progress * 0.4),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2 + progress * 0.3)
],
relativeTime: time
))
}
// Ring appears soft thrum
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2)
],
relativeTime: 0.3,
duration: 0.2
))
// Checkmark pops the payoff snap
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.6)
],
relativeTime: 0.5
))
// Checkmark settles soft bounce
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0.7
))
return try CHHapticPattern(events: events, parameters: [])
}
// Explosion: particles fly out 00.6s glow 0.20.6s checkmark drops in 0.3s fades 1.4s
// Character: massive singular BOOM then debris scattering away the biggest hit of any animation
private func explosionPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// THE explosion maximum everything, low sharpness for deep boom
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0.0
))
// Shockwave rumble deep, low, fading
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.7),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0.02,
duration: 0.35
))
// Debris particles hitting scattered sharp ticks as pieces fly
let debrisTimes: [Double] = [0.12, 0.18, 0.25, 0.32, 0.40]
for (i, time) in debrisTimes.enumerated() {
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: Float(0.5 - Double(i) * 0.08)),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.9)
],
relativeTime: time
))
}
// Checkmark drops in solid medium thud (not as big as the explosion)
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4)
],
relativeTime: 0.55
))
let curve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
CHHapticParameterCurve.ControlPoint(relativeTime: 0.0, value: 1.0),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.15, value: 0.6),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.4, value: 0.1)
],
relativeTime: 0.0
)
return try CHHapticPattern(events: events, parameterCurves: [curve])
}
// Flip: card flips at 0.2s checkmark scales 0.4s shimmer sweeps 0.51.3s
// Character: clean, elegant a crisp two-beat flip then a silky shimmer glide
private func flipPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Card lifts off surface gentle click
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7)
],
relativeTime: 0.2
))
// Card slaps down the satisfying flip landing
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4)
],
relativeTime: 0.4
))
// Checkmark bounce
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0.6
))
// Shimmer sweeping across very light, high-sharpness continuous (like a sparkle)
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.15),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.5,
duration: 0.8
))
let shimmerCurve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
CHHapticParameterCurve.ControlPoint(relativeTime: 0.5, value: 0.0),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.7, value: 1.0),
CHHapticParameterCurve.ControlPoint(relativeTime: 1.3, value: 0.0)
],
relativeTime: 0.0
)
return try CHHapticPattern(events: events, parameterCurves: [shimmerCurve])
}
// Shatter: explode out 00.5s pause converge 0.61.1s checkmark 1.1s fade 1.8s
// Character: glass breaking sharp crack, scattered sharp hits, silence, then magnetic pull + click
private func shatterPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Glass crack extremely sharp and crisp
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.0
))
// Shards flying sharp, glassy ticks scattering outward
let shardTimes: [Double] = [0.04, 0.08, 0.13, 0.19, 0.26, 0.35, 0.45]
for (i, time) in shardTimes.enumerated() {
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: Float(0.6 - Double(i) * 0.07)),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: time
))
}
// ---- silence / gap while pieces float (0.50.6s) ----
// Reform pieces pulling back, low magnetic hum building
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0.6,
duration: 0.5
))
let reformCurve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
CHHapticParameterCurve.ControlPoint(relativeTime: 0.6, value: 0.1),
CHHapticParameterCurve.ControlPoint(relativeTime: 1.0, value: 0.6)
],
relativeTime: 0.0
)
// Final snap together satisfying click as pieces lock in
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.6)
],
relativeTime: 1.1
))
return try CHHapticPattern(events: events, parameterCurves: [reformCurve])
}
// Pulse: waves at 0.0, 0.1, 0.2 stamp checkmark at 0.2s with rotation fade 1.2s
// Character: rhythmic heartbeat pulses then a heavy rubber-stamp DOWN
private func pulseWavePattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Three wave pulses each softer and more diffuse, like ripples spreading
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2)
],
relativeTime: 0.0
))
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.15)
],
relativeTime: 0.1
))
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.25),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0.2
))
// STAMP DOWN heavy, authoritative, like a rubber stamp slamming
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0.25
))
// Stamp reverb brief low rumble from the impact
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.05)
],
relativeTime: 0.27,
duration: 0.2
))
return try CHHapticPattern(events: events, parameters: [])
}
// Fireworks: 4 bursts staggered at 0.0/0.15/0.30/0.45 checkmark at 0.6s fade 2.0s
// Character: distinct pops at different heights/intensities, each with a quick sparkle trail
private func fireworksPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Burst 1 big opening burst
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7)
],
relativeTime: 0.0
))
// Sparkle trail
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.08
))
// Burst 2 smaller, higher pitch
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.9)
],
relativeTime: 0.15
))
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.22
))
// Burst 3 deep thump
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0.30
))
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.25),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8)
],
relativeTime: 0.37
))
// Burst 4 grand finale, biggest
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5)
],
relativeTime: 0.45
))
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.9)
],
relativeTime: 0.52
))
// Checkmark gentle after the show
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4)
],
relativeTime: 0.65
))
return try CHHapticPattern(events: events, parameters: [])
}
// Morph: blobs expand 00.4s morph/shift 0.40.9s converge 0.91.3s checkmark 1.2s
// Character: liquid, organic continuous flowing hum that breathes and undulates
private func morphPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Blob expand soft, round, growing
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.05)
],
relativeTime: 0.0,
duration: 0.4
))
// Morph shift blobs moving, undulating, slightly sharper
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
],
relativeTime: 0.4,
duration: 0.5
))
// Intensity curve for the morph breathing/pulsing feel
let morphCurve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
CHHapticParameterCurve.ControlPoint(relativeTime: 0.0, value: 0.3),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.2, value: 0.5),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.4, value: 0.3),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.6, value: 0.6),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.8, value: 0.4),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.9, value: 0.8)
],
relativeTime: 0.0
)
// Converge blobs snap together, soft thud
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.15)
],
relativeTime: 0.9
))
// Checkmark soft glow reveal, not sharp
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2)
],
relativeTime: 1.2
))
return try CHHapticPattern(events: events, parameterCurves: [morphCurve])
}
// Tunnel: 6 rings zoom in (each 0.1s delay) starburst rotates checkmark 0.5s fade 1.5s
// Character: accelerating whoosh rings pass you faster and faster, then BAM you arrive
private func tunnelPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Rings passing accelerating rhythm (gaps get shorter), each ring is a whoosh
// Visual: rings at 0.0, 0.1, 0.2, 0.3, 0.4, 0.5 with scale animation
let ringTimes: [Double] = [0.0, 0.08, 0.14, 0.19, 0.23, 0.26]
for (i, time) in ringTimes.enumerated() {
let progress = Float(i) / Float(ringTimes.count)
// Each ring hit gets more intense and slightly sharper
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.15 + progress * 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4 + progress * 0.3)
],
relativeTime: time
))
}
// Warp speed continuous buzzy rush as you fly through
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7)
],
relativeTime: 0.28,
duration: 0.2
))
let warpCurve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
CHHapticParameterCurve.ControlPoint(relativeTime: 0.28, value: 0.4),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.38, value: 1.0),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.48, value: 0.8)
],
relativeTime: 0.0
)
// Arrival big thud as you land at the destination
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5)
],
relativeTime: 0.5
))
// Starburst shimmer light rotating sparkle
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.15),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.9)
],
relativeTime: 0.55,
duration: 0.3
))
return try CHHapticPattern(events: events, parameterCurves: [warpCurve])
}
// Gravity: falls 00.4s impact 0.4s bounce up 0.40.6 down 0.6 up 0.60.75 down 0.75 settle 0.85
// Character: weighty falling acceleration, HEAVY impact, realistic diminishing bounces
private func gravityPattern() throws -> CHHapticPattern {
var events: [CHHapticEvent] = []
// Falling accelerating continuous that builds
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.15),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2)
],
relativeTime: 0.0,
duration: 0.38
))
let fallCurve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
CHHapticParameterCurve.ControlPoint(relativeTime: 0.0, value: 0.05),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.2, value: 0.2),
CHHapticParameterCurve.ControlPoint(relativeTime: 0.38, value: 0.9)
],
relativeTime: 0.0
)
// FIRST IMPACT heaviest, low and boomy (this is a heavy object hitting ground)
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.15)
],
relativeTime: 0.4
))
// Dust/ring burst from impact
events.append(CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0.41,
duration: 0.12
))
// Bounce 1 lands again, lighter
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.55),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2)
],
relativeTime: 0.6
))
// Bounce 2 smaller
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.25)
],
relativeTime: 0.75
))
// Final settle tiny tap
events.append(CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.12),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2)
],
relativeTime: 0.85
))
return try CHHapticPattern(events: events, parameterCurves: [fallCurve])
}
}