IOS performs an action after a period of inactivity (no user interaction)

How to add a timer to my iOS application based on user interaction (or lack thereof)? In other words, if within 2 minutes there is no user interaction, I want the application to do something, in this case, go to the initial view controller. If at 1:55 someone touches the screen, the timer is reset. I would think that it should be a global timer, so no matter what kind of view you are in, the lack of interaction starts the timer. Although, I could create a unique timer on each view. Does anyone have any suggestions, links, or code examples where this has been done before?

+46
ios xcode timer
Nov 10 '11 at 19:28
source share
6 answers

The link provided by Anne was a great starting point, but being n00b, which I am, was difficult to translate into my existing project. I found a blog [the original blog no longer exists] that gave the best step by step, but it was not written for Xcode 4.2 and using storyboards. Here is a record of how I got an inactivity timer to work in my application:

  • Create a new file -> Objective-C class -> enter a name (in my case TIMERUIApplication) and change the subclass to UIApplication. You may need to manually enter this in the subclass field. You should now have the corresponding .h and .m files.

  • Modify the .h file as follows:

    #import <Foundation/Foundation.h> //the length of time before your application "times out". This number actually represents seconds, so we'll have to multiple it by 60 in the .m file #define kApplicationTimeoutInMinutes 5 //the notification your AppDelegate needs to watch for in order to know that it has indeed "timed out" #define kApplicationDidTimeoutNotification @"AppTimeOut" @interface TIMERUIApplication : UIApplication { NSTimer *myidleTimer; } -(void)resetIdleTimer; @end 
  • Modify the .m file to read as follows:

     #import "TIMERUIApplication.h" @implementation TIMERUIApplication //here we are listening for any touch. If the screen receives touch, the timer is reset -(void)sendEvent:(UIEvent *)event { [super sendEvent:event]; if (!myidleTimer) { [self resetIdleTimer]; } NSSet *allTouches = [event allTouches]; if ([allTouches count] > 0) { UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase; if (phase == UITouchPhaseBegan) { [self resetIdleTimer]; } } } //as labeled...reset the timer -(void)resetIdleTimer { if (myidleTimer) { [myidleTimer invalidate]; } //convert the wait period into minutes rather than seconds int timeout = kApplicationTimeoutInMinutes * 60; myidleTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO]; } //if the timer reaches the limit as defined in kApplicationTimeoutInMinutes, post this notification -(void)idleTimerExceeded { [[NSNotificationCenter defaultCenter] postNotificationName:kApplicationDidTimeoutNotification object:nil]; } @end 
  • Go to the Supporting Files folder and change main.m to this (unlike previous versions of Xcode):

     #import <UIKit/UIKit.h> #import "AppDelegate.h" #import "TIMERUIApplication.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, NSStringFromClass([TIMERUIApplication class]), NSStringFromClass([AppDelegate class])); } } 
  • Write the remaining code in the AppDelegate.m file. I left code not related to this process. There are no changes to the .h file.

     #import "AppDelegate.h" #import "TIMERUIApplication.h" @implementation AppDelegate @synthesize window = _window; -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil]; return YES; } -(void)applicationDidTimeout:(NSNotification *) notif { NSLog (@"time exceeded!!"); //This is where storyboarding vs xib files comes in. Whichever view controller you want to revert back to, on your storyboard, make sure it is given the identifier that matches the following code. In my case, "mainView". My storyboard file is called MainStoryboard.storyboard, so make sure your file name matches the storyboardWithName property. UIViewController *controller = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"mainView"]; [(UINavigationController *)self.window.rootViewController pushViewController:controller animated:YES]; } 

Notes. The timer starts at any time when a touch is detected. This means that if the user touches the main screen (in my case "mainView"), without even moving from this view, the same view will click on itself after the allotted time. Not very important for my application, but it can be for you. The timer will only reset after touch recognition. If you want to reset the timer, as soon as you return to the page you want to be on, include this code after ... pushViewController: controller animated: YES];

 [(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer]; 

This will make the view click every x minutes if it just sits there without interaction. The timer will reset every time it recognizes a touch, so that it will still work.

Please comment if you suggested improvements, especially sometime, to turn off the timer if "mainView" is currently displayed. It seems I can not understand what the if statement is so that it registers the current view. But I am pleased with where I am. Below is my initial attempt at an if statement so that you can see where I'm heading.

 -(void)applicationDidTimeout:(NSNotification *) notif { NSLog (@"time exceeded!!"); UIViewController *controller = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"mainView"]; //I've tried a few varieties of the if statement to no avail. Always goes to else. if ([controller isViewLoaded]) { NSLog(@"Already there!"); } else { NSLog(@"go home"); [(UINavigationController *)self.window.rootViewController pushViewController:controller animated:YES]; //[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer]; } } 

I am still n00b and may not have done all the best. Suggestions are always welcome.

+110
Jan 24 2018-12-12T00:
source share

Background [quick fix]

There was a request to update this answer with Swift, so I added the snippet below.

Notice that I modified the specifications somewhat for my own purposes: I really want to work if there are no UIEvents for 5 seconds. Any incoming UIEvent press cancels previous timers and restarts a new timer.

Differences from the answer above

  • Some changes to the accepted answer above: instead of setting the first timer on the first event, I immediately set my timer to init() . Also, my reset_idle_timer() will cancel the previous timer, so that only one timer will work at any time.

IMPORTANT: 2 steps to the building

Thanks to a couple of great answers to SO, I was able to adapt the code above as Swift code.

  • Follow this answer for a brief description of how to subclass UIApplication in Swift. Make sure you follow these steps for Swift or the snippet below will not compile. Since the related answer describes the steps so well, I will not repeat here. It takes less than a minute to read and configure it correctly.

  • I could not work NSTimer cancelPreviousPerformRequestsWithTarget: so I found this updated GCD solution that works fine. Just drop this code into a separate .swift file and you will be gtg (so you can call delay() and cancel_delay() and use dispatch_cancelable_closure ).

IMHO, the code below is easy enough to understand. I apologize in advance for not answering any questions on this issue (a bit flooded with atm).

I just posted this answer to contribute to THAT what excellent information I received.

Excerpt

 import UIKit import Foundation private let g_secs = 5.0 class MYApplication: UIApplication { var idle_timer : dispatch_cancelable_closure? override init() { super.init() reset_idle_timer() } override func sendEvent( event: UIEvent ) { super.sendEvent( event ) if let all_touches = event.allTouches() { if ( all_touches.count > 0 ) { let phase = (all_touches.anyObject() as UITouch).phase if phase == UITouchPhase.Began { reset_idle_timer() } } } } private func reset_idle_timer() { cancel_delay( idle_timer ) idle_timer = delay( g_secs ) { self.idle_timer_exceeded() } } func idle_timer_exceeded() { println( "Ring ----------------------- Do some Idle Work!" ) reset_idle_timer() } } 
+14
Dec 19 '14 at 23:01
source share

I implemented what Bobby suggested, but in Swift. Below is the code.

  • Create a new file -> Swift File -> enter a name (in my case TimerUIApplication) and change the subclass to UIApplication. + Change the file TimerUIApplication.swift to read as follows:

     class TimerUIApplication: UIApplication { static let ApplicationDidTimoutNotification = "AppTimout" // The timeout in seconds for when to fire the idle timer. let timeoutInSeconds: TimeInterval = 5 * 60 var idleTimer: Timer? // Listen for any touch. If the screen receives a touch, the timer is reset. override func sendEvent(event: UIEvent) { super.sendEvent(event) if event.allTouches?.first(where: { $0.phase == .began }) != nil { resetIdleTimer() } } // Resent the timer because there was user interaction. func resetIdleTimer() { idleTimer?.invalidate() idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(AppDelegate.idleTimerExceeded), userInfo: nil, repeats: false) } // If the timer reaches the limit as defined in timeoutInSeconds, post this notification. func idleTimerExceeded() { Foundation.NotificationCenter.default.post(name: NSNotification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil) } } 
  • Create a new file -> Swift File -> main.swift (name is important).

     import UIKit UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate)) 
  • In AppDelegate app: Remove @UIApplicationMain above AppDelegate.

     class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AppDelegate.applicationDidTimout(_:)), name: TimerUIApplication.ApplicationDidTimoutNotification, object: nil) return true } ... // The callback for when the timeout was fired. func applicationDidTimout(notification: NSNotification) { if let vc = self.window?.rootViewController as? UINavigationController { if let myTableViewController = vc.visibleViewController as? MyMainViewController { // Call a function defined in your view controller. myMainViewController.userIdle() } else { // We are not on the main view controller. Here, you could segue to the desired class. let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil) let vc = storyboard.instantiateViewControllerWithIdentifier("myStoryboardIdentifier") } } } } 

Keep in mind that you may have to do different things in applicationDidTimout depending on your root view controller. See this post for more information on how you should use your view controller. If you have modal views above the navigation controller, you can use visibleViewController instead of topViewController .

+12
Mar 02 '16 at 10:18
source share

Notes. The timer starts at any time when a touch is detected. This means that if the user touches the main screen (in my case "mainView") even without moving from this point of view, the same point of view will be after the allotted time. Not a big deal for my application, but for yours maybe. The timer will only reset after pressing recognized. If you want to reset the timer, as soon as you return to the page on which you want to be, enable this code after ... pushViewController: animated controller: YES];

One solution to this problem with the same kind of display start that displays again is to have BOOL in appdelegate and set it to true when you want to check that the user is in standby mode and set it to false when you moved to standby view. Then in TIMERUIApplication, the idleTimerExceeded method has an if statement, as shown below. In the viewDidload view of all the views where you want to verify that the user is inactive, you set appdelegate.idle to true, if there are other views in which you do not need to check that the user is in standby mode, you can set it to false.

 -(void)idleTimerExceeded{ AppDelegate *appdelegate = [[UIApplication sharedApplication] delegate]; if(appdelegate.idle){ [[NSNotificationCenter defaultCenter] postNotificationName: kApplicationDidTimeOutNotification object:nil]; } } 
+4
Jan 24 '13 at 11:05
source share

Swift 3 example here

  • create a class like.

      import Foundation import UIKit extension NSNotification.Name { public static let TimeOutUserInteraction: NSNotification.Name = NSNotification.Name(rawValue: "TimeOutUserInteraction") } class InterractionUIApplication: UIApplication { static let ApplicationDidTimoutNotification = "AppTimout" // The timeout in seconds for when to fire the idle timer. let timeoutInSeconds: TimeInterval = 15//15 * 60 var idleTimer: Timer? // Listen for any touch. If the screen receives a touch, the timer is reset. override func sendEvent(_ event: UIEvent) { super.sendEvent(event) // print("3") if idleTimer != nil { self.resetIdleTimer() } if let touches = event.allTouches { for touch in touches { if touch.phase == UITouchPhase.began { self.resetIdleTimer() } } } } // Resent the timer because there was user interaction. func resetIdleTimer() { if let idleTimer = idleTimer { // print("1") idleTimer.invalidate() } idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(self.idleTimerExceeded), userInfo: nil, repeats: false) } // If the timer reaches the limit as defined in timeoutInSeconds, post this notification. func idleTimerExceeded() { print("Time Out") NotificationCenter.default.post(name:Notification.Name.TimeOutUserInteraction, object: nil) //Go Main page after 15 second let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.window = UIWindow(frame: UIScreen.main.bounds) let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) let yourVC = mainStoryboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController appDelegate.window?.rootViewController = yourVC appDelegate.window?.makeKeyAndVisible() } } 
  • create another class named main.swift insert the following code

     import Foundation import UIKit CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)) { argv in _ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self)) } 
  • don't forget to remove @UIApplicationMain from AppDelegate

  • Swift 3 source code is provided by GitHub. GitHub Link: https://github.com/enamul95/UserInactivity

+3
Aug 16 '17 at 18:49 on
source share

Swift 3.0 Convert UIApplication to Vanessa UIApplication Answer

 class TimerUIApplication: UIApplication { static let ApplicationDidTimoutNotification = "AppTimout" // The timeout in seconds for when to fire the idle timer. let timeoutInSeconds: TimeInterval = 5 * 60 var idleTimer: Timer? // Resent the timer because there was user interaction. func resetIdleTimer() { if let idleTimer = idleTimer { idleTimer.invalidate() } idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(TimerUIApplication.idleTimerExceeded), userInfo: nil, repeats: false) } // If the timer reaches the limit as defined in timeoutInSeconds, post this notification. func idleTimerExceeded() { NotificationCenter.default.post(name: NSNotification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil) } override func sendEvent(_ event: UIEvent) { super.sendEvent(event) if idleTimer != nil { self.resetIdleTimer() } if let touches = event.allTouches { for touch in touches { if touch.phase == UITouchPhase.began { self.resetIdleTimer() } } } } } 
+2
Mar 29 '17 at 7:32
source share



All Articles