Pause NSTimer when app goes to background - ios

I have the following in my touchesBegan function in my GameScene so that the timer only starts when the user touches a certain sprite:
let start = SKAction.runBlock({
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "printDuration:", userInfo: NSDate(), repeats: true)
})
tapToStartNode.runAction(start)
and the following function:
func printDuration(timer: NSTimer) {
if self.view?.paused == false {
guard let userInfo = timer.userInfo else {
return
}
guard let startDate = userInfo as? NSDate else {
return
}
let duration = NSDate().timeIntervalSinceDate(startDate)
currentTime = NSDate().timeIntervalSinceDate(startDate)
currentTimeValueLabel.text = "\(NSString(format:"%3.2f", duration))"
}
}
However, when applicationDidEnterBackground or applicationWillResignActive the timer is not paused and continues to run when the app is in the backgorund. How do I make it stop?

Here a tips you can use for your problem
You can use func applicationDidEnterBackground(_ application: UIApplication) or func applicationWillResignActive(_ application: UIApplication) on AppDelegate
Access your controller by initialize rootViewController example :
let vc = self.window?.rootViewController as? ViewController
Then you can access your function (make sure function is on public state)
vc.pauseTimer() (customize based on your situation)
Hope it will be helpful

Related

How to give count down for OTP in Swift

I need to give 60 sec time countdown for resend otp. and if i click verifyOtp butn then count down to be stop. and i want that otp to be expaire. in count down time resend otpbutton to be disable.. please suggest me how to set count down for resendotp.
i am getting otp in registrService().
do{
var json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: Any]
let regUserStatus = json["status"] as? String
if regUserStatus == "sucess"
{
print("the json regggggggggggis \(json)")
let phNum = json["mobile_number"] as? Int
let status = json["status"] as? String
self.otpField = json["otp"] as? Int
}
else{
DispatchQueue.main.async {
self.registerButton.isHidden = false
self.sendOtpButton.isHidden = true
self.otpTextField.isHidden = true
self.resendButn.isHidden = true
self.otpcountLabel.isHidden = true
AlertFun.ShowAlert(title: "", message: "user exist", in: self)
}
}
}
resendButton .
#IBAction func resendOtpButn(_ sender: Any) {
print("resendotp tapped")
registerService()
}
After you have started the OTP process you need to create a 60 seconds timer that when it expires it runs a selector method:
create a class level Timer property:
var otpTimer: Timer?
create the timer when your reg button is clicked
#IBAction func regButn(_ sender: Any) {
registerService()
startTimer()
}
func startTimer() {
optTimer?.invalidate(). //cancels it if already running
optTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(timerDidFire(_:)), userInfo: userInfo, repeats: false)
}
When your timer hits zero the selector will be called, and you can do whatever you want to do to expire the otp request
#objc func timerDidFire(_ timer: Timer) {
// timer has completed. Do whatever you want...
}
if the resend button is tapped, I think you want to restart the timer, so ...
#IBAction func resendOtpButn(_ sender: Any) {
registerService()
startTimer()
}
You will probably also want to cancel the timer if your otp completes successfully, so in your success completion handler you can just do
self.optTimer?.invalidate()

How to make a global timer for the whole app

I want to make a timer that start on app load and when it finishes to alert a text and restart the app when alert is closed(when click the ok button of the alert). Have tried with a class timer but I can't send alert and reload the app. I need it to be working in the whole app, and when move to other view not restart it countdown. Is there any easy way without using CocoaPods.
import Foundation
class GlobalTokenTimer {
static let sharedTimer: GlobalTokenTimer = {
let timer = GlobalTokenTimer()
return timer
}()
var internalTimer: Timer?
var jobs = [() -> Void]()
func startTimer(withInterval interval: Double, andJob job: #escaping () -> Void) {
if internalTimer != nil {
internalTimer?.invalidate()
}
jobs.append(job)
internalTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(doJob), userInfo: nil, repeats: true)
}
func stopTimer() {
guard internalTimer != nil else {
print("No timer active, start the timer before you stop it.")
return
}
jobs = [()->()]()
internalTimer?.invalidate()
}
#objc func doJob() {
guard jobs.count > 0 else{return}
}
}
Your timer is static and will work while app is alive. Implement some function that will show alert on topViewController of your application.
extension UIApplication {
// function that lets you get topViewController of the app
func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
Inside your app delegate start your timer and show alert. In iOS you can't restart you app, but can set newly created MyStartViewController which is your home page for example and restart logic of your app.
UPD:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func startTimer() {
GlobalTokenTimer.sharedTimer.startTimer(withInterval: 10) {
let alertVC = UIAlertController(title: "Restart your app!", message: "Select ok!", preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "Restart", style: .default) { [weak self] _ in
self?.window?.rootViewController = MyStartViewController()
startTimer()
})
application.topViewController()?.present(alertVC, animated: true)
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
startTimer()
}
}
Set your timer repeat: false and it would stop after finishing once.
internalTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(doJob), userInfo: nil, repeats: false)
UPD:
You do nothing in your doJob function, change it to this.
#objc func doJob() {
guard jobs.count > 0 else { return }
jobs.forEach { $0() }
}

Send the location to the server every n minutes in the background in swift

i want to sent the location to the server when the app is background, i tried with many way but can't get the solution
I tried with the timer function using the timer and using function
self.bgtimer = Timer.scheduledTimer(timeInterval:60, target: self, selector: #selector(AppDelegate.bgtimer(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(self.bgtimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
but i when i stop the timer when the app becomes active
if UIApplication.shared.applicationState == .active {
RunLoop.current.cancelPerformSelectors(withTarget: self.bgtimer)
timer.invalidate()
}
but the app not function properly collection view and back function everything is not working properly
In DidBackground:
stop normalTimer.
start New backgroundTaskTimer
In WillForeground:
stop backgroundTaskTimer.
start normalTimer
Find below sample code:
In Appdelegate:
Declaration:
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var backgroundTaskTimer:Timer! = Timer()
Implementation:
func doBackgroundTask() {
DispatchQueue.global(qos: .default).async {
self.beginBackgroundTask()
if self.backgroundTaskTimer != nil {
self.backgroundTaskTimer.invalidate()
self.backgroundTaskTimer = nil
}
//Making the app to run in background forever by calling the API
self.backgroundTaskTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.startTracking), userInfo: nil, repeats: true)
RunLoop.current.add(self.backgroundTaskTimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
// End the background task.
self.endBackgroundTask()
}
}
func beginBackgroundTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(withName: "Track trip", expirationHandler: {
self.endBackgroundTask()
})
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
In ApplicationDidEnterBackground:
func applicationDidEnterBackground(_ application: UIApplication) {
//Start the background fetch
self.doBackgroundTask()
}
In ApplicationWillEnterForeground:
func applicationWillEnterForeground(_ application: UIApplication) {
if self.backgroundTaskTimer != nil {
self.backgroundTaskTimer.invalidate()
self.backgroundTaskTimer = nil
}
}

Background location update every n minutes

I'm writing an application that requires location updates every n minutes and sends data to a server even when the application is in background mode. I have gone through so many links about this task. As I found the proper solution is to use a timer and make it as a background task.
Concerning to this I have two questions:
How can I implement these regular background location updates? I understand that Apple allows background tasks for an only certain time. So how can I make long-running background timer?
Will apple reject the application with such kind of logics?
inside appdelegate create method like this
func getLocation()
{
locationManager.allowsBackgroundLocationUpdates = true
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
inside didFinishLaunchingWithOptions method in appdelegate
var n = 10 //seconds
var timer = Timer.scheduledTimer(timeInterval: n, target: self, selector: #selector(getLocation), userInfo: nil, repeats: true)
set the delegate method for cllocationmanager
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
//get the coordinates or do some code
}
Use following code.This will work for 3 minutes
Call this didenterbackground or foreground
var time = 180
func applicationDidEnterBackground(_ application: UIApplication)
{
self.doBackgroundTask()
}
var timerBack = Timer()
func doBackgroundTask() {
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.beginBackgroundUpdateTask()
print("Background time remaining = \(UIApplication.shared.backgroundTimeRemaining) seconds")
self.timerBack = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AppDelegate.displayAlert), userInfo: nil, repeats: true)
RunLoop.current.add(self.timerBack, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
self.endBackgroundUpdateTask()
}
}
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
//Do whatever you want
func displayAlert{
}

NSTimer invalidate not work

I can not stop the timer NSTimer created to perform secondary operations.
I have read and tried all sorts of possible Web guides but none have worked and the timer continues to run even when the Controller is destroyed. Below is the code in question:
/* Copy banner in a temp array and change order to random value */
private func getBanners(){
let realm = try! Realm()
let banners = self.synchronizer.getAllBanners()?.sorted("ordine")
if let banners = banners {
for banner in banners {
let random = Double.random(0.0, 1.0)
let currentOrderValue = banner.ordine
self.banners.append(banner)
do {
try realm.write({
self.banners.last!.ordine = currentOrderValue + random
})
} catch let error as NSError {
print(error)
}
}
}
}
/*Update index of banner to show */
func updateIndex(timer : NSTimer) {
if self.index+1 < self.banners.count {
for banner in self.banners{
print(banner.name + " \(banner.ordine)")
}
self.index+=1
} else {
self.index = 0
}
self.setImageBanner()
}
/* Set image of banner to show*/
private func setImageBanner() {
if self.banners.count > 0 {
self.bannerImage.hidden = false
let banner = self.banners[self.index]
let file = banner.images[0]
self.synchronizer.loadImageURL(file.link, imageView: self.bannerImage)
} else {
self.bannerImage.hidden = true
}
}
/* Start Timer */
func startTimerForBanners() {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.getBanners()
})
self.timer = NSTimer(timeInterval: 5, target: self, selector: #selector(self.updateIndex(_:)), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.timer!, forMode: NSRunLoopCommonModes)
}
/* Open link on banner click */
func openLink(sender : UITapGestureRecognizer){
if self.index >= 0 && self.index < self.banners.count {
let banner = self.banners[self.index]
print(banner.name)
self.synchronizer.openLink(banner.url)
}
}
func trialLesson(sender : UITapGestureRecognizer){
performSegueWithIdentifier("trial_lesson_segue", sender: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if let timer = self.timer {
timer.invalidate()
}
}
you need to invalidate the timer before starting a new timer. Currently I believe you are calling "startTimerForBanners" many times may be, so many timer thread has been initiated. One approach is to invlidate all timer one by one or just invalidate before starting new one like this
/* Start Timer */
func startTimerForBanners() {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.getBanners()
})
**self.timer.invalidate()**
self.timer = NSTimer(timeInterval: 5, target: self, selector: #selector(self.updateIndex(_:)), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.timer!, forMode: NSRunLoopCommonModes)
}
Are you sure that your controller is really deallocated?
There is one thing you should be always aware about NSTimer: it retains it's target.
I would suggest to:
Start your timer in viewWillAppear
Invalidate and set timer to nil in viewDidDisappear
Also try replacing your start timer code with this (Swift 3):
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.updateIndex(_:)), userInfo: nil, repeats: true)

Resources