Hello There!, I'm currently working on an App with an implemented timer. It was initially planned, that the User will get a notification when the timer ends. Everything works fine until the timer ends and the phone doesn't gets any notification... This is my code:
import SwiftUI
import Combine
import UserNotifications
struct TimerView: View { @State private var timeRemaining: TimeInterval @State private var timerActive = false @Binding var studyTime: Int @Binding var selectedExam: Exam
init(studyTime: Binding<Int>, selectedExam: Binding<Exam>) {
_studyTime = studyTime
_selectedExam = selectedExam
_timeRemaining = State(initialValue: TimeInterval(studyTime.wrappedValue * 60))
}
var body: some View {
VStack {
ZStack {
Circle()
.trim(from: 0, to: CGFloat(timeRemaining / (TimeInterval(studyTime * 60))))
.stroke(Color.purple, lineWidth: 15)
.rotationEffect(.degrees(-90))
.animation(.linear(duration: 1))
.padding(40)
Text("\(timeRemaining.formattedTime)")
.font(.system(size: 50))
}
Button(action: {
self.timerActive.toggle()
}) {
Text(timerActive ? "Stop" : "Start")
.font(.title)
.padding()
}
.foregroundColor(.white)
.background(timerActive ? Color.red : Color.green)
.cornerRadius(10)
.padding()
}
.onReceive(timer) { _ in
guard self.timerActive else { return }
if self.timeRemaining > 0 {
self.timeRemaining -= 1
} else {
self.timerActive = false
sendNotification()
}
print("Time Remaining: \(self.timeRemaining)")
}
.navigationTitle($selectedExam.wrappedValue.subject)
.navigationBarBackButtonHidden(true)
.onDisappear {
// Actions if the timer View disappears
}
}
var timer: AnyPublisher<Date, Never> {
Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default)
.autoconnect()
.eraseToAnyPublisher()
}
func sendNotification() {
let content = UNMutableNotificationContent()
content.title = "Lernzeit vorbei"
content.body = "Deine Lernzeit für \(selectedExam.subject) ist abgelaufen!"
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "timerNotification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Fehler beim Hinzufügen der Benachrichtigung zur Warteschlange: \(error.localizedDescription)")
} else {
print("Benachrichtigung zur Warteschlange hinzugefügt")
}
}
}
}
extension TimeInterval { var formattedTime: String { let minutes = Int(self) / 60 % 60 let seconds = Int(self) % 60 return String(format: "%02i:%02i", minutes, seconds) } }
I looked it up and the app is allowed to send every type of notification... (this is initialized in another part of the code)
This approach will fail to show notifications as implemented here. How it fails depends on whether the app is in the foreground or the background.
If the app is in the background, it will be suspended after a few seconds and will not get any runtime. It is not possible to run a timer in the background. Once your app gets suspended, it will not be given CPU time to execute any code, including timers. Similarly, if your app gets terminated, either due to user force closing it, or the system terminating it, there will be no opportunity for your app to execute any code.
You would be able to prevent suspension only in a select cases if your app is using one of the supported background modes (like navigating or playing audio in the background). But it is not permitted to use these background modes for the sole purpose of trying to keep your app active so it can run a timer. If you are going to use any of these background modes, your app requires to have a prominent feature that makes use of whatever background mode you choose to use.
If, on the other hand, the app is staying in the foreground all the time, there will be no issues with the timer, but the notification will not be shown. If a notification triggers when your app is running in the foreground, the system delivers that notification directly to your app instead of displaying as usual. Instead the function userNotificationCenter(_:willPresent:withCompletionHandler:) will be called and then you can handle the notification and choose to display it or not depending on your use case. This is explained further in Handle notifications while your app runs in the foreground