Iphone: Detecting User Inactivity/Idle Time Since Last Screen Touch

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");

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 :)

Detecting user inactivity in any VC

Here's how I do it in my app. Create the timer to check every so often:

self.timer = NSTimer.scheduledTimerWithTimeInterval(
10, target: self, selector: "decrementScore", userInfo: nil, repeats: true)

Now we know that if decrementScore() is called, 10 seconds of idle time went by. If the user does something, we need to restart the timer:

func resetTimer() {
self.timer?.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(
10, target: self, selector: "decrementScore", userInfo: nil, repeats: true)
}

Detect Touch in Base Class

So, I figured out a way. In my base class, I was setting tap gesture on view earlier.

    let tap =  UITapGestureRecognizer(target: self, action: nil)
tap.delegate = self
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)

so instead of view.addGestureRecognizer(tap) now I am adding gesture on window

    if let window = UIApplication.shared.keyWindow {
window.addGestureRecognizer(tap)
}

and I implemented UIGestureRecognizerDelegate

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 
// My Code here
return true
}

So, Now my base class is able to detect any taps and calling the delegate.

Please Note: Adding this delegate/ gesture is not effecting the touch or gesture on any of the other views of my child classes.



Related Topics



Leave a reply



Submit