iPhone:检测自上次屏幕触摸以来的用户不活动/空闲时间

有没有人实现一个function,如果用户没有触摸屏幕一段时间,你采取了一定的行动? 我试图找出最好的方法来做到这一点。

UIApplication中有一个与此有点相关的方法:

[UIApplication sharedApplication].idleTimerDisabled; 

如果你有这样的事情会很好:

 NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed; 

然后,我可以设置一个计时器并定期检查这个值,并在超过阈值时采取一些行动。

希望解释我在找什么。 有没有人已经解决了这个问题,或有任何想法,你会怎么做呢? 谢谢。

这是我一直在寻找的答案:

让你的应用程序委托子类UIApplication。 在实现文件中,像这样覆盖sendEvent:方法:

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

maxIdleTime和idleTimer是实例variables。

为了这个工作,你还需要修改你的main.m来告诉UIApplicationMain使用你的委托类(在本例中是AppDelegate)作为主类:

 int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate"); 

我有一个空闲计时器的解决scheme,不需要inheritanceUIApplication的变化。 它在一个特定的UIViewController子类上工作,所以如果你只有一个视图控制器(就像一个交互式的应用程序或者游戏一样),或者只想处理特定的视图控制器中的空闲超时,那么它就很有用。

每当空闲计时器重置时,它也不会重新创buildNSTimer对象。 如果定时器触发,它只会创build一个新的。

您的代码可以调用resetIdleTimer来处理可能需要使空闲计时器无效的任何其他事件(例如重要的加速计input)。

 @interface MainViewController : UIViewController { NSTimer *idleTimer; } @end #define kMaxIdleTimeSeconds 60.0 @implementation MainViewController #pragma mark - #pragma mark Handling idle timeout - (void)resetIdleTimer { if (!idleTimer) { idleTimer = [[NSTimer scheduledTimerWithTimeInterval:kMaxIdleTimeSeconds target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO] retain]; } else { if (fabs([idleTimer.fireDate timeIntervalSinceNow]) < kMaxIdleTimeSeconds-1.0) { [idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:kMaxIdleTimeSeconds]]; } } } - (void)idleTimerExceeded { [idleTimer release]; idleTimer = nil; [self startScreenSaverOrSomethingInteresting]; [self resetIdleTimer]; } - (UIResponder *)nextResponder { [self resetIdleTimer]; return [super nextResponder]; } - (void)viewDidLoad { [super viewDidLoad]; [self resetIdleTimer]; } @end 

(为简洁起见,不包括内存清理代码)。

这个线程是一个很好的帮助,我把它包装成一个发送通知的UIWindow子类。 我select了通知,使其成为一个真正的松耦合,但是您可以轻松地添加一个委托。

要点是:

http://gist.github.com/365998

此外,UIApplication子类问题的原因是NIB被设置为创build2个UIApplication对象,因为它包含应用程序和委托。 UIWindow的子类很好用。

我遇到了一个由运动控制的游戏,即禁用了屏幕locking,但在菜单模式下应该再次启用它。 我没有使用定时器,而是将所有对setIdleTimerDisabled调用封装在一个小类中,提供以下方法:

 - (void) enableIdleTimerDelayed { [self performSelector:@selector (enableIdleTimer) withObject:nil afterDelay:60]; } - (void) enableIdleTimer { [NSObject cancelPreviousPerformRequestsWithTarget:self]; [[UIApplication sharedApplication] setIdleTimerDisabled:NO]; } - (void) disableIdleTimer { [NSObject cancelPreviousPerformRequestsWithTarget:self]; [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; } 

disableIdleTimer取消激活空闲计时器,当进入菜单时使用enableIdleTimerDelayed ,或者在空闲计时器处于活动状态时运行,并enableIdleTimer AppDelegate的applicationWillResignActive方法调用enableIdleTimer以确保所有更改都被正确重置为系统默认行为。
我写了一篇文章,并提供了iPhone游戏中的单例类IdleTimerManager 空闲计时器处理的代码

这是检测活动的另一种方法:

定时器被添加到UITrackingRunLoopMode ,所以只有在有UITracking活动时才能触发。 它还有一个很好的优点,就是不会把所有触摸事件都发送给你,从而通知上一个ACTIVITY_DETECT_TIMER_RESOLUTION秒是否有活动。 我将select器命名为keepAlive因为这似乎是一个合适的用例。 你当然可以做任何你想要的信息,最近有活动。

 _touchesTimer = [NSTimer timerWithTimeInterval:ACTIVITY_DETECT_TIMER_RESOLUTION target:self selector:@selector(keepAlive) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:_touchesTimer forMode:UITrackingRunLoopMode]; 

其实子类的想法很好。 只是不要让你的委托的UIApplication子类。 创build另一个从UIApplicationinheritance的文件(例如myApp)。 在IB中,将fileOwner对象的类设置为myApp并在myApp.m中实现上述的sendEvent方法。 在main.m中:

 int retVal = UIApplicationMain(argc,argv,@"myApp.m",@"myApp.m") 

等voilà!

对于swift v 3.1

不要忘了在AppDelegate // @ UIApplicationMain注释这一行

 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 * 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 == UITouchPhase.began { self.resetIdleTimer() } } } } // 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(self.idleTimerExceeded), userInfo: nil, repeats: false) } // If the timer reaches the limit as defined in timeoutInSeconds, post this notification. func idleTimerExceeded() { NotificationCenter.default.post(name:Notification.Name.TimeOutUserInteraction, object: nil) } } 

创buildmain.swif文件并添加(名字很重要)

 CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)) {argv in _ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self)) } 

观察任何其他class级的通知

 NotificationCenter.default.addObserver(self, selector: #selector(someFuncitonName), name: Notification.Name.TimeOutUserInteraction, object: nil) 

最终,您需要定义您认为是空闲的 – 闲置的用户没有触摸屏幕的结果,或者是没有使用计算资源的系统状态? 在许多应用中,即使不通过触摸屏与设备进行主动交互,用户也有可能做一些事情。 虽然用户可能熟悉设备进入睡眠的概念以及通过屏幕变暗发生的通知,但如果他们闲置,他们不一定会发生什么事情 – 您需要小心关于你会做什么。 但回到原来的说法 – 如果你认为第一个案例是你的定义,没有真正简单的方法来做到这一点。 您需要接收每个触摸事件,并根据需要将其传递给响应者链,同时注意接收事件的时间。 这会给你一些做闲置计算的基础。 如果您认为第二种情况是您的定义,那么您可以使用NSPostWhenIdle通知来尝试当时的逻辑。

这些都显得过于复杂。 我相信这是一个更清晰的方式来代理所有需要的触摸。

 fileprivate var timer ... //timer logic here @objc public class CatchAllGesture : UIGestureRecognizer { override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { super.touchesBegan(touches, with: event) } override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) { //reset your timer here state = .failed super.touchesEnded(touches, with: event) } override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) { super.touchesMoved(touches, with: event) } } @objc extension YOURAPPAppDelegate { func addGesture () { let aGesture = CatchAllGesture(target: nil, action: nil) aGesture.cancelsTouchesInView = false self.window.addGestureRecognizer(aGesture) } } 

在你的应用程序的委托的完成启动方法,只需调用addGesture,你就全部设置。 所有的触及将通过CatchAllGesture的方法,而不会阻止其他人的function。