iOS perform action after period of inactivity (no user interaction)
The link that Anne provided was a great starting point, but, being the n00b that I am, it was difficult to translate into my existing project. I found a blog [original blog no longer exists] that gave a better step-by-step, but it wasn't written for XCode 4.2 and using storyboards. Here is a write up of how I got the inactivity timer to work for my app:
Create a new file -> Objective-C class -> type in a name (in my case TIMERUIApplication) and change the subclass to UIApplication. You may have to manually type this in the subclass field. You should now have the appropriate .h and .m files.
Change the .h file to read as follows:
#import
//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;
@endChange 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 || phase == UITouchPhaseMoved)
{
[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];
}
@endGo into your Supporting Files folder and alter main.m to this (different from prior versions of XCode):
#import
#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 your AppDelegate.m file. I've left out code not pertaining to this process. There is no change to make in 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 will start anytime a touch is detected. This means that if the user touches the main screen (in my case "mainView") even without navigating away from that view, the same view will push over itself after the allotted time. Not a big deal for my app, but for yours it might be. The timer will only reset once a touch is recognized. If you want to reset the timer as soon as you get back to the page you want to be at, include this code after the ...pushViewController:controller animated:YES];
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
This will cause the view to push every x minutes if it's just sitting there with no interaction. The timer will still reset every time it recognizes a touch, so that will still work.
Please comment if you have suggested improvements, especially someway to disable the timer if the "mainView" is currently being displayed. I can't seem to figure out my if statement to get it to register the current view. But I'm happy with where I'm at. Below is my initial attempt at the if statement so you can see where I was going with it.
-(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 a n00b and may have not done everything the best way. Suggestions are always welcome.
Detecting inactivity (no user interaction) in iOS for showing separate Screen like an Overlay
From @Prabhat Kesara's comment and @Vanessa Forney answer in the helper link. I come with the below solution and it is working fine. If someone comes with the requirement like this they can use this
import UIKit
import Foundation
extension NSNotification.Name {
public static let TimeOutUserInteraction: NSNotification.Name = NSNotification.Name(rawValue: "TimeOutUserInteraction")
}
class UserInterractionSetup: UIApplication {
static let ApplicationDidTimoutNotification = "AppTimout"
// The timeout in seconds for when to fire the idle timer.
let timeoutInSeconds: TimeInterval = 1 * 60 //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 idleTimer != nil {
self.resetIdleTimer()
}
if let touches = event.allTouches {
for touch in touches {
if touch.phase == UITouch.Phase.began {
self.resetIdleTimer()
}
}
}
}
// Resent the timer because there was user interaction.
func resetIdleTimer() {
if let idleTimer = idleTimer {
// print("1")
let root = UIApplication.shared.keyWindow?.rootViewController
root?.dismiss(animated: true, completion: nil)
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.
@objc func idleTimerExceeded() {
print("Time Out")
NotificationCenter.default.post(name:Notification.Name.TimeOutUserInteraction, object: nil)
let screenSaverVC = UIStoryboard(name:"ScreenSaver", bundle:nil).instantiateViewController(withIdentifier:"LandingPageViewController") as! LandingPageViewController
if let presentVc = TopMostViewController.sharedInstance.topMostViewController(controller: screenSaverVC){
let root: UINavigationController = UIApplication.shared.keyWindow!.rootViewController as! UINavigationController
if let topView = root.viewControllers.last?.presentedViewController {
topView.dismiss(animated: false) {
root.viewControllers.last?.navigationController?.present(presentVc, animated: true, completion: nil)
}
}else if let topViewNavigation = root.viewControllers.last {
topViewNavigation.navigationController?.present(presentVc, animated: true, completion: nil)
}
}
}
}
the difficulty I was faced was presenting overlay or screensaver above an already presented a view. That case, also we are handling in the above solution.
Happy coding :)
iPhone: Detecting user inactivity/idle time since last screen touch
Here's the answer I had been looking for:
Have your application delegate subclass UIApplication. In the implementation file, override the sendEvent: method like so:
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
// Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
// allTouches count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded)
[self resetIdleTimer];
}
}
- (void)resetIdleTimer {
if (idleTimer) {
[idleTimer invalidate];
[idleTimer release];
}
idleTimer = [[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO] retain];
}
- (void)idleTimerExceeded {
NSLog(@"idle time exceeded");
}
where maxIdleTime and idleTimer are instance variables.
In order for this to work, you also need to modify your main.m to tell UIApplicationMain to use your delegate class (in this example, AppDelegate) as the principal class:
int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate");
Related Topics
How to Resize Uiimageview Based on Uiimage's Size/Ratio in Swift 3
Swift Apply .Uppercasestring to Only the First Letter of a String
Wait Until Multiple Networking Requests Have All Executed - Including Their Completion Blocks
Adding a Uilabel to a Uitoolbar
How to Fix "No Valid 'Aps-Environment' Entitlement String Found for Application" in Xcode 4.3
Duplicate Symbol Error in Objective-C Build
Uipopoverpresentationcontroller on iPhone Doesn't Produce Popover
Custom Uitableview Section Index
What Is the Meaning of the "No Index Path for Table Cell Being Reused" Message in iOS 6/7
With Auto Layout, How to Make a Uiimageview's Size Dynamic Depending on the Image
Dynamic Height Issue for Uitableview Cells (Swift)
How to Get Uikeyboard Size with iOS
App "Does Not Contain the Correct Beta Entitlement"
The Code Signature Version Is No Longer Supported