본문으로 건너뛰기

Live Activity (위젯) 트러블슈팅

·5 min read

문제 상황

  • 일시정지해도 위젯 타이머가 계속 실행됨
  • 자동 일시정지 시 앱은 5초인데 위젯은 4초로 표시
  • 위젯 일시정지 버튼 눌러도 앱에서 아무 변화 없음
  • 러닝 중과 일시정지 시 타이머 폰트가 다름
  • 위젯에 로고 이미지가 표시되지 않음
  • 여러 번 pause/resume 시 시간 누적 오류
  • 백그라운드에서 첫 auto-pause 후 이후 auto-pause가 작동하지 않음

원인 분석

  • 일시정지 타이머 계속 실행: LiveActivityManager.update()에서 isPaused=true여도 startTimeInMillisecondsnil로 설정하지 않아 위젯이 타이머를 계속 구동

  • 자동 일시정지 시간 불일치: JS 스톱워치 시간과 Native에서 계산하는 시간이 별도로 관리되어 미세한 차이 발생

  • 위젯 버튼 작동 안 함: 딥링크 처리에서 isRunningRef.current 조건이 실제 workoutState와 불일치

  • 폰트 불일치: 러닝 중 Text(timerInterval:)에는 .monospacedDigit() 적용, 일시정지 Text(duration)에는 미적용

  • 이미지 안 보임: Image(imageName) 직접 사용 시 위젯 확장에서 Asset 접근 불가

  • 시간 누적 오류: pauseStartDate 초기화 누락으로 일시정지 시간이 잘못 계산됨

  • 백그라운드 auto-pause 실패: JS 타이머(setTimeout)는 앱이 백그라운드로 가면 suspend됨. 첫 번째 auto-pause는 앱이 포그라운드일 때 작동하지만, 이후 앱이 백그라운드 상태에서는 JS 코드가 실행되지 않아 auto-pause 로직이 동작하지 않음

해결 과정

  1. LiveActivityManager.update() 수정:

    • isPaused=true일 때 startTimeInMilliseconds = nil 설정
    • 위젯이 타이머 대신 고정된 duration 문자열 표시
  2. JS-Native 시간 동기화:

    • WorkoutSessionManager.pauseWorkout(elapsedTimeMs) 파라미터 추가
    • 브릿지 파일(WorkoutSessionManager.m) 수정
    • JS에서 정확한 경과 시간을 Native로 전달
  3. 딥링크 처리 개선:

    • isRunningRef.current 조건 제거
    • workoutState만으로 판단하도록 변경
    • 딥링크 URL 형식 통일 (action=pause, action=resume)
  4. 폰트 일관성 적용:

    • 모든 타이머 Text에 .monospacedDigit() 추가
    • LiveActivityView.swift, LiveActivityWidget.swift 수정
  5. 이미지 로딩 수정:

    • resizableImage(imageName:) 헬퍼 함수 사용
    • Image.dynamic() 통해 Asset과 파일 경로 모두 지원
  6. 일시정지 시간 추적 변수 추가:

    • totalPausedDuration: 누적 일시정지 시간
    • pauseStartDate: 현재 일시정지 시작 시점
    • resume 시 정확히 초기화
  7. Native Auto-Pause 구현:

    • 문제: JS 기반 auto-pause는 앱이 백그라운드일 때 작동하지 않음 (JS 타이머 suspend)
    • 해결: GPS 위치 업데이트를 받는 LocationTracker.swift에서 직접 auto-pause 처리
    • checkAutoPause(currentSpeed:): 속도 0.3 m/s 미만이면 5초 후 자동 일시정지
    • triggerAutoPause(): Live Activity 즉시 업데이트 + JS에 이벤트 전송
    • triggerAutoResume(): 속도 감지 시 3초 후 자동 재개
    • JS에서는 autoPauseStateChanged 이벤트를 수신하여 상태 동기화

배운 점 및 회고

  • SwiftUI Text(timerInterval:): startTimeInMilliseconds가 설정되면 위젯이 자동으로 타이머 구동. 일시정지 시 반드시 nil로 설정 필요
  • JS-Native 동기화: 시간처럼 정확도가 중요한 값은 한쪽에서 계산하여 전달하는 것이 안전
  • 딥링크 상태 체크: 여러 상태 변수를 AND 조건으로 체크하면 동기화 문제 발생 가능. 하나의 신뢰할 수 있는 상태로 판단
  • 위젯 확장 제약: 위젯 확장은 메인 앱과 별도 프로세스이므로 Asset 접근 시 헬퍼 함수 사용 권장
  • 백그라운드 실행: JS 타이머는 앱이 백그라운드로 가면 suspend됨. 백그라운드에서도 작동해야 하는 기능(auto-pause 등)은 반드시 Native에서 처리