SwiftUI · Таймер фона работает на симуляторе, но не на реальном устройстве - PullRequest
3 голосов
/ 11 июля 2020

Я пытаюсь создать таймер, который продолжает обратный отсчет, когда приложение находится в фоновом режиме или даже когда экран заблокирован. После того, как таймер достигнет 0, должно быть отправлено уведомление.

Пока это работает на симуляторе, но не на реальном устройстве (iPhone X, работает iOS 13.5.1). При переходе в фоновый режим задача просто приостанавливается.

Как мне сохранить обратный отсчет на реальном устройстве?

import SwiftUI
import UserNotifications

struct ContentView: View {
    @State var start = false
    @State var count = 10 // 10 sec timer
    @State var time = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View{
        VStack{
            Text("\(self.count)")
            
            Button(action: {
                self.start.toggle()
            }) {
                Text("Start")
            }
        }
        .onAppear(perform: {
            UNUserNotificationCenter.current().requestAuthorization(options: [.badge,.sound,.alert]) { (_, _) in
            }
        })
            .onReceive(self.time) { (_) in
                if self.start{
                    if self.count != 0{
                        self.count -= 1
                    }
                    else{
                        self.sendNotification()
                    }
                }
        }
    }
    
    func sendNotification(){
        
        let content = UNMutableNotificationContent()
        content.title = "Timer"
        content.body = "Time is up!"
        
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
        let req = UNNotificationRequest(identifier: "MSG", content: content, trigger: trigger)
        
        UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
    }
}

Выбранные фоновые режимы (снимок экрана)

1 Ответ

3 голосов
/ 11 июля 2020

Apple не позволяет таймерам работать в фоновом режиме.

Для получения дополнительной информации см. Эти два вопроса:

Apple не позволит вам запускать процессы в течение длительного времени после того, как ваше приложение было фоновым. Из документов:

Реализация длительных задач

Для задач, требующих большего времени выполнения для реализации, вы должны запросить определенные c разрешения для их запуска в фон без их приостановки. В iOS только определенные типы приложений c могут работать в фоновом режиме:]

  • Приложения, которые воспроизводят звуковой контент для пользователя в фоновом режиме, например, музыку c player app
  • Приложения, которые записывают аудиоконтент в фоновом режиме
  • Приложения, которые постоянно информируют пользователей об их местонахождении, например, приложение для навигации
  • Приложения, поддерживающие голос через Inte rnet Протокол (VoIP)
  • Приложения, которым необходимо регулярно загружать и обрабатывать новый контент
  • Приложения, которые регулярно получают обновления от внешних аксессуаров
  • Приложения, реализующие эти службы должны объявить службы, которые они поддерживают, и использовать системные структуры для реализации соответствующих аспектов этих служб.

Объявление служб позволяет системе узнать, какие службы вы используете, но в некоторых случаях именно системные структуры фактически предотвратить приостановку вашего приложения.

Но в этом случае вы хотите отправить уведомление, поэтому я предлагаю сделать что-то подобное, но с особенностями.

Когда пользователь покидает приложение, вы хотите узнать текущее время. Затем вы хотите добавить оставшееся время таймера к текущему времени и запустить код в своем приложении для отправки уведомления в текущее время + оставшееся время таймера.

Код для отправки уведомления в указанное время c может смотреть здесь :

let content = UNMutableNotificationContent()
                content.title = "Title"
                content.body = "Body"
                content.sound = UNNotificationSound.default()

                let gregorian = Calendar(identifier: .gregorian)
                let now = Date()
                var components = gregorian.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now)

                // Change the time to 7:00:00 in your locale
                components.hour = 7
                components.minute = 0
                components.second = 0

                let date = gregorian.date(from: components)!

                let triggerDaily = Calendar.current.dateComponents([.hour,.minute,.second,], from: date)
                let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: true)


                let request = UNNotificationRequest(identifier: CommonViewController.Identifier, content: content, trigger: trigger)
                print("INSIDE NOTIFICATION")

                UNUserNotificationCenter.current().add(request, withCompletionHandler: {(error) in
                    if let error = error {
                        print("SOMETHING WENT WRONG")
                    }
                })
...