简体   繁体   English

当用户隐藏应用程序或关闭设备时,swiftui 中的自定义计时器停止在后台获取中计数

[英]Custom timer in swiftui stops to count in background fetch when user hide app or turn off device

I'm trying to use this cool timer from kavsoft我正在尝试使用kavsoft 的这个很酷的计时器

But when i hide app or turn off screen device (not close app) this timer after a couple of seconds or minutes in the background mode stops counting and stops.但是,当我在后台模式下隐藏应用程序或关闭屏幕设备(不关闭应用程序)时,此计时器会在几秒钟或几分钟后停止计数并停止。 If I open the application, it continues to count down again.如果我打开应用程序,它会继续倒计时。

I tried on the original code and on my modified one.我尝试了原始代码和修改后的代码。 In mine, I made the format of hours minutes seconds.在我的,我制作了小时分钟秒的格式。 In any of them, counting in background mode stops working.在其中任何一个中,后台模式下的计数都会停止工作。 Is any way to fix it?有什么办法可以解决吗? it may take up to 2 hours for my needs in app to work in background mode.我在应用程序中的需求可能需要长达 2 小时才能在后台模式下工作。 Here is my modifed code in swift这是我在 swift 中的修改代码

import SwiftUI
import UserNotifications

    struct TimerDiscovrView : View {
        
        @State var start = false
        @State var to : CGFloat = 0
        @State var MaxCount = 0
        @State var count = 0
        var testTimer: String
        var testName: String
        @State var time = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
        
        var body: some View{
            ZStack{
                //заливка экрана
                Color.black.opacity(0.06).edgesIgnoringSafeArea(.all)
                VStack{
                    
                    ZStack{
                        Circle()
                        .trim(from: 0, to: 1)
                            .stroke(Color.black.opacity(0.09), style: StrokeStyle(lineWidth: 35, lineCap: .round))
                        .frame(width: 280, height: 280)
                        
                        Circle()
                            .trim(from: 0, to: self.to)
                            .stroke(Color.red, style: StrokeStyle(lineWidth: 35, lineCap: .round))
                        .frame(width: 280, height: 280)
                        .rotationEffect(.init(degrees: -90))
                        
                        
                        VStack{
                            
                            Text("\(valueFormat(mCount: count/3600)):\(valueFormat(mCount: count/60%60)):\(valueFormat(mCount: count%60))")
                                .font(.system(size: 45))
                                .fontWeight(.bold)
                                .padding(.top)
                                .padding(.bottom)
                            
                            Text(LocalizedStringKey("Total time")).lineLimit(2)
                            Text("\(valueFormat(mCount: MaxCount/3600)):\(valueFormat(mCount: MaxCount/60%60)):\(valueFormat(mCount: MaxCount%60))")
                                .font(.title)
                        }
                    }
                    
                    VStack {
                        HStack(spacing: 20){
                            
                            Button(action: {
                                
                                if self.count == MaxCount {
                                    
                                    self.count = 0
                                    withAnimation(.default){
                                        
                                        self.to = 0
                                    }
                                }
                                self.start.toggle()
                                
                            }) {
                                
                                HStack(spacing: 15){
                                    
                                    Image(systemName: self.start ? "pause.fill" : "play.fill")
                                        .foregroundColor(.white)
                                    //текст на кнопке
        //                            Text(self.start ? "Pause" : "Play")
        //                                .foregroundColor(.white)
                                }
                                .padding(.vertical)
                                .frame(width: (UIScreen.main.bounds.width / 2) - 55)
                                .background(Color.red)
                                .clipShape(Capsule())
                                .shadow(radius: 6)
                            }
                            
                            Button(action: {
                                
                                self.count = 0
                                
                                withAnimation(.default){
                                    
                                    self.to = 0
                                }
                                
                            }) {
                                
                                HStack(spacing: 15){
                                    
                                    Image(systemName: "arrow.clockwise")
                                        .foregroundColor(.red)
                                    //текст на кнопке
        //                            Text("Restart")
        //                                .foregroundColor(.red)
                                    
                                }
                                .padding(.vertical)
                                .frame(width: (UIScreen.main.bounds.width / 2) - 55)
                                .background(
                                
                                    Capsule()
                                        .stroke(Color.red, lineWidth: 2)
                                )
                                .shadow(radius: 6)
                            }
                            
    
    
                        }
                        Text(LocalizedStringKey("Set timer for")).font(.subheadline).lineLimit(1)
                        Text("\(testName)").font(.title).lineLimit(2)
                        VStack {
                        Text(LocalizedStringKey("Attention")).font(.footnote).foregroundColor(.gray).fixedSize(horizontal: false, vertical: true)
                        }.padding(.horizontal)
                    }
                    .padding(.top)
                    .padding(.bottom, 30)
    
                }
                
            }.navigationBarTitle(LocalizedStringKey("Set timer"), displayMode: .inline)
            .onAppear(perform: {
                if self.MaxCount == 0 {
                    let arrayTimer = testTimer.split(separator: " ")
                    if arrayTimer.count > 1 {
                      
                        let counts = Int(arrayTimer[0]) ?? 0
                        
                        /// Преобразование в секунды
                        switch arrayTimer[1] {
                        case "min":
                            self.MaxCount = counts*60
                        case "hour":
                            self.MaxCount = counts*3600
                        case "hours":
                            self.MaxCount = counts*3600
                        default:
                            self.MaxCount = counts
                        }
      
                    }
                    
                }
                
                UNUserNotificationCenter.current().requestAuthorization(options: [.badge,.sound,.alert]) { (_, _) in
                }
            })
            .onReceive(self.time) { (_) in
                
                if self.start{
                    
                    if self.count != MaxCount {
                        
                        self.count += 1
                        print("hello")
                        
                        withAnimation(.default){
                            
                            self.to = CGFloat(self.count) / CGFloat(MaxCount)
                        }
                    }
                    else {
                    
                        self.start.toggle()
                        self.Notify()
                    }
    
                }
                
            }
        }
        
        func Notify(){
            
            let content = UNMutableNotificationContent()
            
            /// key - ключ_локализованной_фразы, comment не обязательно заполнять
            content.title = NSLocalizedString("Perhaps your test is ready", comment: "") 
            
            /// с аргументами (key заменяете на нужное)
            // Вид локализованной строки в файлах локализации "key %@"="It,s time to check your %@";
            content.body = String.localizedStringWithFormat(NSLocalizedString("It's time to check your %@", comment: ""), self.testName)
            
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
            
            let req = UNNotificationRequest(identifier: "MSG", content: content, trigger: trigger)
            
            UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
        }
        
        func valueFormat(mCount: Int) -> String {
            String(format: "%02d", arguments: [mCount])
        }
        
        
    }

I had this problem before, and I was struggling to fix it, however I didn't get any useful answer.我以前遇到过这个问题,我一直在努力解决它,但是我没有得到任何有用的答案。 I tried to activate Background Modes without success.我试图激活Background Modes但没有成功。

Here it is how did I overcome this problem:这是我如何克服这个问题的:

Let's imagine that your counter has started counting, the counter now is at 5 seconds , then suddenly the user enter the background what do we need to do?假设你的计数器已经开始计数,现在计数器是5 seconds ,然后突然用户进入后台,我们需要做什么?

  1. Let's go to SceneDelegate and declare those variables让我们 go 到SceneDelegate并声明这些变量

    var appIsDeadAt: Double? var appIsBackAliveAt: Double?
  2. We need to save the time when the user enter the background in sceneDidEnterBackground我们需要在sceneDidEnterBackground中保存用户进入后台的时间

    appIsDeadAt = Date().timeIntervalSince1970
  3. When user enter the application again, we need to calculate the time that the application stayed in background.当用户再次进入应用程序时,我们需要计算应用程序在后台停留的时间。 Go to sceneWillEnterForeground and get the time when the application is back active Go 到sceneWillEnterForeground并获取应用程序重新激活的时间

    appIsBackAliveAt = Date().timeIntervalSince1970
  4. Now we need to calculate the total time that the application stayed in the background现在我们需要计算应用程序在后台停留的总时间

    let finalTime = (appIsBackAliveAt! - appIsDeadAt!)
  5. Finally, let's say that the application stayed in background for 10 seconds and the previous time is 5 by that 5 + finalTime (Which is 10 seconds) the total time would be 15 seconds , and then update your counter time to continue counting from 15 seconds .最后,假设应用程序在后台停留了10 seconds ,之前的时间是5乘以5 + finalTime (即 10 秒),总时间为15 seconds ,然后更新您的计数器时间以从15 seconds开始计数.

Note: use UserDefaults to pass the values to your counter, to make it easy for you.注意:使用UserDefaults将值传递给您的计数器,以方便您。

One real time example is from my own application that is released on applestore: Chatiw一个实时示例来自我自己在 Apple Store 上发布的应用程序: Chatiw

Basically of my timer, is when the user watch an Ads video I'll reward him with a 300 seconds without ads.基本上我的计时器是,当用户观看广告视频时,我会奖励他300 seconds没有广告。

After implementing all the steps above, when I have the final time I'll store it on UserDefaults :完成上述所有步骤后,当我最后一次将其存储在UserDefaults上时:

userDefaults.setValue(finalTime.rounded(), forKey: "timeInBg")

Then on my main code that contain the counter I'll update the counter to the new time:然后在包含计数器的主代码上,我会将计数器更新为新时间:

adsRemovalCounter = adsRemovalCounter - Int(self.userDefaults.double(forKey: "timeInBg"))

After that I'll delete the UserDefaults key for the finalTime so it won't interfere with the next calculation when the user enter the background again.之后,我将删除finalTimeUserDefaults键,这样当用户再次进入后台时,它就不会干扰下一次计算。

self.userDefaults.removeObject(forKey: "timeInBg")

Here it is an example in SwiftUI that I just made:这是我刚刚制作的SwiftUI中的一个示例:

ContentView File: ContentView文件:

import SwiftUI

struct ContentView: View {

@ObservedObject var counterService = CounterService()

var body: some View {
    
    
    VStack {
        Text("\(self.counterService.counterTime)")
            .font(.largeTitle)
            .fontWeight(.bold)
            .foregroundColor(Color.white)
            .frame(width: 100, height: 80)
            .background(Color.red)
            .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
            .shadow(color: Color.red.opacity(0.5), radius: 10, x: 5, y: 2)
            .padding()
            .padding(.bottom, 100)
        
        
        Button(action: {
            self.counterService.startCounting()
        }) {
            Text("Start Counter")
                .font(.title)
                .fontWeight(.bold)
                .foregroundColor(Color.white)
                .frame(width: 200, height: 80)
                .background(Color.gray)
                .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                .shadow(color: Color.black.opacity(0.5), radius: 10, x: 5, y: 2)
        }
    }

}
    
}

CounterService File: CounterService文件:

import SwiftUI

class CounterService: ObservableObject {

@Published var counterTime: Int = 0


func startCounting(){
    
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
        
        
        if UserDefaults.standard.string(forKey: "timeInBg") != nil {
            self.counterTime = Int(UserDefaults.standard.double(forKey: "timeInBg")) + self.counterTime
            UserDefaults.standard.removeObject(forKey: "timeInBg")
        }
        self.counterTime += 1
        
    }
    
}

}

SceneDelegate File: SceneDelegate文件:

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

let userDefaults = UserDefaults.standard
var appIsDeadAt: Double?
var appIsBackAliveAt: Double?


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    let contentView = ContentView().environment(\.managedObjectContext, context)

    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
}

func sceneDidDisconnect(_ scene: UIScene) {

}

func sceneDidBecomeActive(_ scene: UIScene) {

}

func sceneWillResignActive(_ scene: UIScene) {

}

func sceneWillEnterForeground(_ scene: UIScene) {
    appIsBackAliveAt = Date().timeIntervalSince1970
    
    if appIsDeadAt != nil && appIsBackAliveAt != nil {
        let finalTime = (appIsBackAliveAt! - appIsDeadAt!)
        userDefaults.setValue(finalTime.rounded(), forKey: "timeInBg")
    }
    
    
}

func sceneDidEnterBackground(_ scene: UIScene) {
    appIsDeadAt = Date().timeIntervalSince1970
    (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}


}

Voila: here you go the result:瞧:这里是 go 结果:

结果

I hope this answer your question.我希望这能回答你的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 当用户关闭“ Background App Refresh”时,在后台模式下更新位置不起作用 - Update location on background mode do not work when user turn off “Background App Refresh” SwiftUI·后台定时器在模拟器上工作,但在真实设备上不工作 - SwiftUI · Background timer works on simulator but not on real device 当我的应用程序(phonegap 3.1)进入后台时,GPS关闭 - GPS turn off when my app (phonegap 3.1) go in background 如何以编程方式打开和关闭后台获取 - How to turn background fetch on and off programatically 当用户使用 Firebase FCM swizzling 关闭 iOS 设备设置中的通知时,如何实现应用行为? - How to implement app behavior when user turn off notification in device setting for iOS using Firebase FCM swizzling turned on? 我安排了一个计时器使用readRSSI函数读取RSSI,但是当我的应用进入后台时计时器停止 - i schedule a Timer to read RSSI using readRSSI function ,but the timer stops when my app go into background 用户在APP中关闭通知时如何接收通知 - how to receive notification when user turn off notification in APP 即使后台获取已关闭,如何在没有通知的情况下接收消息 - How to received messages without Notifications even background fetch is turn off Xcode-Swift 5:在仅 WiFi 的设备上启动 iOS 应用程序时是否可以关闭 GPS 要求? - Xcode-Swift 5: Is it possible to turn off the GPS requirement when launching iOS app on a WiFi only device? SpriteKit每隔x秒关闭/打开节点一次,关闭时隐藏 - SpriteKit turn node off/on every x seconds and hide when off
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM