[iOS] Background Timer

Charlie·2023년 6월 25일

앱에서 타이머를 사용할 때, 앱이 background 상태로 진입을 하게 되면 타이머는 멈추게 된다.
이를 해결하기 위해 앱이 background로 진입한 시간과 다시 돌아온 시간의 차를 구해서 타이머를 다시 시작할 수 있게 만들었다.





Enter background

SceneDelegate에서 background로 진입 시 다음 2가지 task를 해준다.

  • UserDefaults에 현재 시각 저장 -> foreground가 되었을 때 시간 계산을 하기 위해
  • NotificationCenter에 background로 진입했다고 알려주기 -> 타이머를 invalidate하기 위해

func sceneDidEnterBackground(_ scene: UIScene) {
    UserDefaults.standard.setValue(Date(), forKey: Constants.User.backgroundEntryTime)
    NotificationCenter.default.post(name: Notification.Name(Constants.App.sceneDidEnterBackground),
                                    object: nil)
}




Enter foreground

앱이 foreground로 진입했을 때 Userdefaults에 저장한 시각을 통해 얼마나 지났는지 시간을 계산해주고, 이를 NotificationCenter를 통해 전달한 후 UserDefaults의 값은 필요없기 때문에 지워준다.


func sceneWillEnterForeground(_ scene: UIScene) {
    guard let backgroundEntryTime = UserDefaults.standard.object(forKey: Constants.User.backgroundEntryTime) as? Date else { return }
    let elapsedSecondsFromBackground = Int(Date().timeIntervalSince(backgroundEntryTime))
    
    NotificationCenter.default.post(name: Notification.Name(rawValue: Constants.App.sceneWillEnterForeground),
                                    object: nil,
                                    userInfo: [Constants.NotificationCenterKey.elapsedSecondsFromBackground: elapsedSecondsFromBackground])
    
    UserDefaults.standard.removeObject(forKey: Constants.User.backgroundEntryTime)
}




Timer

타이머쪽에서는 NotificationCenterrx를 사용하여 observer를 추가해준다.

  • Background로 진입 시 : 타이머 invalidate
  • Foreground로 진입 시 : background로부터 지난 시간을 전달받아 타이머를 다시 시작

private func addSceneBackgroundNotificationObserver() {
    NotificationCenter.default.rx.notification(Notification.Name(Constants.App.sceneDidEnterBackground))
        .asDriver(onErrorRecover: { _ in .never() })
        .drive(with: self) { target, _ in
            target.invalidateTimer()
        }
        .disposed(by: self.disposeBag)
}

private func addSceneForegroundNotificationObserver() {
    NotificationCenter.default.rx.notification(Notification.Name(Constants.App.sceneWillEnterForeground))
        .asDriver(onErrorRecover: { _ in .never() })
        .drive(with: self) { target, notification in
            guard let elapsedSecondsFromBackground = notification.userInfo?[Constants.NotificationCenterKey.elapsedSecondsFromBackground] as? Int else { return }
            
            target.remainingSeconds -= elapsedSecondsFromBackground
            target.startTimer()
        }
        .disposed(by: self.disposeBag)
}
profile
Hello

0개의 댓글