① 다이나믹 아일랜드 멈춤(2중 방어): - 오디오 키퍼 하드닝 — volume 0.1(0.0은 유지 풀림 사례)·prepareToPlay·인터럽션/씬 전환 revive() - 엔진을 벽시계 기준으로 재설계 — 전 국면이 sessionStart로부터 결정적(computeTarget), 타이머는 경계 재장전용, scenePhase .active에서 resyncAfterWake()로 정확한 국면 자가 교정 ② 진동 패턴 동일(톡=톡톡): 시스템 바이브는 재생 중 재호출이 무시됨(실측) — 시간 오프셋 방식 폐기, AudioServicesPlaySystemSoundWithCompletion 완료 체이닝으로 재작성 (톡 1방 / 톡톡 0.25s 쉬고 1방 / 드르륵 0.15s 3연타 / 지잉 즉시 이어붙인 3방) ③ 운동 칼로리 0: 빌더 운동은 에너지 샘플 직접 추가 필요 — iOS는 MET 추정(1.8+강도×0.17, 70kg 가정) activeEnergyBurned 샘플 add, 워치는 심박·활성 에너지 읽기 권한 추가로 센서 실측 수집, 권한 문구 3언어 갱신 검증: Debug·Release·워치 빌드 그린, 완주 경로 시뮬 재검증(엔진 재설계 후). DI 갱신·진동 구분·칼로리는 실기기 재확인 필요(재실행 시 '활성 에너지' 권한 시트 뜸) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPmp29DQHY6Ahfss5SRopT
42 lines
1.6 KiB
Swift
42 lines
1.6 KiB
Swift
//
|
||
// Haptics.swift
|
||
// Keging
|
||
//
|
||
// 진동 패턴 재생 (iOS) — 시스템 바이브(1회 ≈0.5초)를 완료 콜백으로 체이닝한다.
|
||
// ⚠️ 재생 중에 같은 시스템 사운드를 다시 호출하면 무시되므로(실기기 실측 — 겹침·짧은
|
||
// 간격 오프셋이 전부 먹혀 패턴이 똑같아지던 버그) 반드시 완료 후 다음 진동을 쏜다.
|
||
// AudioServices 바이브는 무음 스위치 무관, 오디오 세션이 살아 있으면 백그라운드에서도 울린다.
|
||
//
|
||
|
||
import AudioToolbox
|
||
import Foundation
|
||
|
||
nonisolated enum Haptics {
|
||
static func play(_ pattern: HapticPattern) {
|
||
// 각 진동 앞의 쉼(초) — 첫 진동은 즉시
|
||
let gaps: [TimeInterval]
|
||
switch pattern {
|
||
case .short: gaps = [0] // 톡: 1방
|
||
case .doubleTap: gaps = [0, 0.25] // 톡톡: 완료 후 0.25초 쉬고 1방 더
|
||
case .rattle: gaps = [0, 0.15, 0.15] // 드르륵: 짧은 쉼으로 3연타
|
||
case .long: gaps = [0, 0, 0] // 지잉: 완료 즉시 이어 붙여 긴 진동감
|
||
}
|
||
playChain(gaps)
|
||
}
|
||
|
||
private static func playChain(_ gaps: [TimeInterval]) {
|
||
guard let gap = gaps.first else { return }
|
||
let rest = Array(gaps.dropFirst())
|
||
let fire = {
|
||
AudioServicesPlaySystemSoundWithCompletion(kSystemSoundID_Vibrate) {
|
||
DispatchQueue.main.async { playChain(rest) }
|
||
}
|
||
}
|
||
if gap > 0 {
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + gap, execute: fire)
|
||
} else {
|
||
fire()
|
||
}
|
||
}
|
||
}
|