mycode/myApp/Keging/Shared/HapticEnginePlayer.swift
songyc macbook eba01b570f fix(keging): 실기기 2차 피드백 — 중단 UI 인라인화·통계 위로 스와이프·진동 3종 Core Haptics 재설계
① 중단 확인: 하단 시트(confirmationDialog) → 버튼 자리에서 펼쳐지는 인라인 확인
   (안내 문구 + 계속하기(그린)/중단하기(레드), scale+opacity 전환)
② 통계: 아래→위 스와이프로 반전, 하단 중앙 손잡이(위 화살표+캡슐), 통통 튀는 스프링
   (dampingFraction 0.62), 통계 상단에 닫기 손잡이 추가, 닫기=아래로 스와이프
③ 진동 3종(기본·톡톡·지이이잉)으로 재설계 — 체이닝 간격도 길다는 피드백:
   - iOS 포그라운드: Core Haptics 정밀 재생(Shared/HapticEnginePlayer) — 기본 0.25s,
     톡톡 0.14s×2 시작 간격 0.28s(요구 0.5s 미만), 지이이잉 1.2s 연속, 세기 1.0
   - iOS 백그라운드 폴백: 시스템 바이브 완료 체이닝(1방/2방/3방 즉시 잇기)
   - 워치: watchOS SDK에 CoreHaptics 부재(실측) — 강한 시스템 햅틱 매핑
     (기본=.notification, 톡톡=.directionUp, 지이이잉=.retry — 내장이라 간격 뭉개짐 없음)
   - 구버전 저장 패턴 자동 이관, 기본값 수축=지이이잉·이완=기본
- 신규 QA 인자 -confirmStop, 도움말 문구·번역(en/ja) 갱신, stale 정리(카탈로그 0/0)
- 검증: Debug·Release·워치 빌드 그린, 화면 QA(메인 하단 손잡이·통계 시트·중단 인라인 레드)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPmp29DQHY6Ahfss5SRopT
2026-08-30 09:39:37 +09:00

62 lines
2.1 KiB
Swift

//
// HapticEnginePlayer.swift
// Keging
//
// Core Haptics · (watchOS SDK CoreHaptics ).
// 1 .
// 1 , false
// ( ).
//
#if canImport(CoreHaptics)
import CoreHaptics
@MainActor
final class HapticEnginePlayer {
static let shared = HapticEnginePlayer()
private var engine: CHHapticEngine?
private init() {}
@discardableResult
func play(_ pattern: HapticPattern) -> Bool {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return false }
if attempt(pattern) { return true }
engine = nil
return attempt(pattern)
}
private func attempt(_ pattern: HapticPattern) -> Bool {
do {
let engine = try currentEngine()
let events = pattern.hapticEvents.map { spec in
CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: spec.intensity),
CHHapticEventParameter(parameterID: .hapticSharpness, value: spec.sharpness),
],
relativeTime: spec.time,
duration: spec.duration
)
}
let player = try engine.makePlayer(with: try CHHapticPattern(events: events, parameters: []))
try engine.start()
try player.start(atTime: CHHapticTimeImmediate)
return true
} catch {
return false
}
}
private func currentEngine() throws -> CHHapticEngine {
if let engine { return engine }
let newEngine = try CHHapticEngine()
newEngine.isAutoShutdownEnabled = true
engine = newEngine
return newEngine
}
}
#endif