앱에서 타이머를 사용할 때, 앱이 background 상태로 진입을 하게 되면 타이머는 멈추게 된다.
이를 해결하기 위해 앱이 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)
}
앱이 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)
}
타이머쪽에서는 NotificationCenter와 rx를 사용하여 observer를 추가해준다.
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)
}