// // Haptics.swift // Keging // // 진동 패턴 재생 (iOS). // - 포그라운드: Core Haptics 정밀 패턴(HapticEnginePlayer) — 짧은 간격 연타·긴 연속 진동 // - 백그라운드: 시스템 바이브 완료 체이닝 폴백. ⚠️재생 중 같은 시스템 사운드 재호출은 // 무시되므로(실기기 실측) 완료 콜백으로만 잇는다 — 물리적 최소 간격. // AudioServices 바이브는 무음 스위치 무관, 오디오 세션이 살아 있으면 백그라운드에서도 울린다. // import AudioToolbox import UIKit @MainActor enum Haptics { /// 진행 중 체인 취소용 세대 토큰 — 새 패턴이 시작되면 이전 체인의 남은 진동을 버린다 private static var generation = 0 static func play(_ pattern: HapticPattern) { generation += 1 if UIApplication.shared.applicationState == .active, HapticEnginePlayer.shared.play(pattern) { return } playSystemChain(pattern, generation: generation) } /// 백그라운드 폴백 — 완료 즉시 잇는 것이 시스템 바이브의 최소 간격 private static func playSystemChain(_ pattern: HapticPattern, generation: Int) { let count = switch pattern { case .basic: 1 case .doubleTap: 2 case .long: 3 // 이어붙여 긴 진동감 } playChain(remaining: count, generation: generation) } private static func playChain(remaining: Int, generation: Int) { guard remaining > 0, generation == Self.generation else { return } AudioServicesPlaySystemSoundWithCompletion(kSystemSoundID_Vibrate) { @MainActor @Sendable in playChain(remaining: remaining - 1, generation: generation) } } }