[英]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
,然后突然用户进入后台,我们需要做什么?
Let's go to SceneDelegate
and declare those variables让我们 go 到SceneDelegate
并声明这些变量
var appIsDeadAt: Double? var appIsBackAliveAt: Double?
We need to save the time when the user enter the background in sceneDidEnterBackground
我们需要在sceneDidEnterBackground
中保存用户进入后台的时间
appIsDeadAt = Date().timeIntervalSince1970
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
Now we need to calculate the total time that the application stayed in the background现在我们需要计算应用程序在后台停留的总时间
let finalTime = (appIsBackAliveAt! - appIsDeadAt!)
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.之后,我将删除finalTime
的UserDefaults
键,这样当用户再次进入后台时,它就不会干扰下一次计算。
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.