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

标签 ios swift swiftui background-fetch

我正在尝试使用 this cool timer from kavsoft

但是当我隐藏应用程序或关闭屏幕设备(而不是关闭应用程序)时,此计时器在后台模式下几秒或几分钟后停止计数并停止。如果我打开应用程序,它会再次继续倒计时。

我尝试了原始代码和修改后的代码。在我的中,我制作了小时分钟秒的格式。在其中任何一个中,后台模式下的计数都会停止工作。 有办法解决吗? 我的应用程序可能需要长达 2 小时才能在后台模式下运行。 这是我用 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])
        }
        
        
    }

最佳答案

我以前遇到过这个问题,我一直在努力解决它,但是我没有得到任何有用的答案。我尝试激活后台模式但没有成功。

这是我如何克服这个问题的:

假设您的计数器已经开始计数,计数器现在处于5秒,然后用户突然进入后台,我们需要做什么?

  1. 让我们转到 SceneDelegate 并声明这些变量

    var appIsDeadAt: Double?
    var appIsBackAliveAt: Double?
    
  2. 我们需要在sceneDidEnterBackground中保存用户进入后台的时间

    appIsDeadAt = Date().timeIntervalSince1970
    
  3. 当用户再次进入应用程序时,我们需要计算应用程序在后台停留的时间。转到 sceneWillEnterForeground 并获取应用程序恢复事件的时间

    appIsBackAliveAt = Date().timeIntervalSince1970
    
  4. 现在我们需要计算应用程序在后台停留的总时间

    let finalTime = (appIsBackAliveAt! - appIsDeadAt!)
    
  5. 最后,假设应用程序在后台停留了 10 秒,而之前的时间是 5,即 5 + FinalTime > (即 10 秒)总时间将为 15 秒,然后更新计数器时间以从 15 秒 继续计数。

注意:使用UserDefaults将值传递给您的计数器,以方便您。

一个实时示例来 self 自己的应用程序,该应用程序在 Applestore 上发布:Chatiw

我的计时器的基本原理是,当用户观看广告视频时,我会奖励他 300 秒无广告的时间。

完成上述所有步骤后,当我有最后一次时间时,我会将其存储在 UserDefaults 上:

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

然后在包含计数器的主代码上,我将计数器更新为新时间:

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

之后我将删除finalTimeUserDefaults键,这样当用户再次进入后台时它就不会干扰下一步的计算。

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

这是我刚刚在 SwiftUI 中制作的一个示例:

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 文件:

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 文件:

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()
}


}

瞧!结果如下:

Result

我希望这能回答您的问题。

关于ios - 当用户隐藏应用程序或关闭设备时,swiftui 中的自定义计时器停止在后台获取中计数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65341931/

相关文章:

objective-c - iOS、iSGL3D 和立方体

ios - 使用 SwiftUI 创建视差

ios - 如何在特定时间触发来自 firebase 的通知?

ios - 无法在 PreapreForSegue 中设置 NavigationController 的 Outlet 值 TopViewController

iphone - 从服务器下载数据时旋转按钮

ios - 对每个 Collection View 单元格使用单个 TableView Controller

swift - Alamofire 5,如何停止所有请求,刷新访问 token 并重新运行所有停止的请求?

swift - 委托(delegate)方法不会被第二次调用

ios - 添加核心数据项后以编程方式生成 View

ios - 如何在 SwiftUI 中设置 NavigationView 背景颜色