ios iPhone:检测自上次触摸屏幕以来的用户不活动/空闲时间
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/273450/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iPhone: Detecting user inactivity/idle time since last screen touch
提问by Mike McMaster
Has anybody implemented a feature where if the user has not touched the screen for a certain time period, you take a certain action? I'm trying to figure out the best way to do that.
有没有人实现过一个功能,如果用户在一段时间内没有触摸屏幕,你就会采取某种行动?我正在努力找出最好的方法来做到这一点。
There's this somewhat-related method in UIApplication:
UIApplication 中有这个有点相关的方法:
[UIApplication sharedApplication].idleTimerDisabled;
It'd be nice if you instead had something like this:
如果你有这样的东西,那就太好了:
NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;
Then I could set up a timer and periodically check this value, and take some action when it exceeds a threshold.
然后我可以设置一个计时器并定期检查这个值,并在它超过阈值时采取一些措施。
Hopefully that explains what I'm looking for. Has anyone tackled this issue already, or have any thoughts on how you would do it? Thanks.
希望这能解释我正在寻找的东西。有没有人已经解决过这个问题,或者对你将如何做有任何想法?谢谢。
回答by Mike McMaster
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:
让您的应用程序委托子类 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");
}
where maxIdleTime and idleTimer are instance variables.
其中 maxIdleTime 和 idleTimer 是实例变量。
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:
为了使其工作,您还需要修改 main.m 以告诉 UIApplicationMain 使用您的委托类(在本例中为 AppDelegate)作为主体类:
int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate");
回答by Chris Miles
I have a variation of the idle timer solution which doesn't require subclassing UIApplication. It works on a specific UIViewController subclass, so is useful if you only have one view controller (like an interactive app or game may have) or only want to handle idle timeout in a specific view controller.
我有一个空闲计时器解决方案的变体,它不需要子类化 UIApplication。它适用于特定的 UIViewController 子类,因此如果您只有一个视图控制器(如交互式应用程序或游戏可能有)或只想处理特定视图控制器中的空闲超时,则非常有用。
It also does not re-create the NSTimer object every time the idle timer is reset. It only creates a new one if the timer fires.
每次重置空闲计时器时,它也不会重新创建 NSTimer 对象。如果计时器触发,它只会创建一个新的。
Your code can call resetIdleTimer
for any other events that may need to invalidate the idle timer (such as significant accelerometer input).
您的代码可以调用resetIdleTimer
可能需要使空闲计时器无效的任何其他事件(例如重要的加速度计输入)。
@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
(memory cleanup code excluded for brevity.)
(为简洁起见,排除了内存清理代码。)
回答by Sergey Stadnik
For swift v 3.1
对于 swift v 3.1
dont't forget comment this line in AppDelegate //@UIApplicationMain
不要忘记在 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)
}
}
create main.swif file and add this (name is important)
创建 main.swif 文件并添加它(名称很重要)
CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)) {argv in
_ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self))
}
Observing notification in an any other class
在任何其他类中观察通知
NotificationCenter.default.addObserver(self, selector: #selector(someFuncitonName), name: Notification.Name.TimeOutUserInteraction, object: nil)
回答by Brian King
This thread was a great help, and I wrapped it up into a UIWindow subclass that sends out notifications. I chose notifications to make it a real loose coupling, but you can add a delegate easily enough.
这个线程是一个很大的帮助,我把它包装成一个发送通知的 UIWindow 子类。我选择通知以使其成为真正的松散耦合,但您可以轻松添加委托。
Here's the gist:
这是要点:
Also, the reason for the UIApplication subclass issue is that the NIB is setup to then create 2 UIApplication objects since it contains the application and the delegate. UIWindow subclass works great though.
此外,UIApplication 子类问题的原因是 NIB 被设置为创建 2 个 UIApplication 对象,因为它包含应用程序和委托。UIWindow 子类虽然很好用。
回答by Roby
Actually the subclassing idea works great. Just don't make your delegate the UIApplication
subclass. Create another file that inherits from UIApplication
(e.g. myApp). In IB set the class of the fileOwner
object to myApp
and in myApp.m implement the sendEvent
method as above. In main.m do:
实际上,子类化的想法很有效。只是不要让您的委托成为UIApplication
子类。创建另一个继承自的文件UIApplication
(例如 myApp)。在 IB 中将fileOwner
对象的类设置为myApp
并在 myApp.m 中实现上述sendEvent
方法。在 main.m 中:
int retVal = UIApplicationMain(argc,argv,@"myApp.m",@"myApp.m")
et voilà!
等等!
回答by Kay
I just ran into this problem with a game that is controlled by motions i.e. has screen lock disabled but should enable it again when in menu mode. Instead of a timer I encapsulated all calls to setIdleTimerDisabled
within a small class providing the following methods:
我刚刚在一个由动作控制的游戏中遇到了这个问题,即禁用了屏幕锁定,但在菜单模式下应该再次启用它。我将所有调用封装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
deactivates idle timer, enableIdleTimerDelayed
when entering the menu or whatever should run with idle timer active and enableIdleTimer
is called from your AppDelegate's applicationWillResignActive
method to ensure all your changes are reset properly to the system default behaviour.
I wrote an article and provided the code for the singleton class IdleTimerManager Idle Timer Handling in iPhone Games
disableIdleTimer
停用空闲计时器,enableIdleTimerDelayed
当进入菜单或任何应该在空闲计时器活动的情况下运行并enableIdleTimer
从您的 AppDelegateapplicationWillResignActive
方法调用时,以确保您的所有更改都正确重置为系统默认行为。
我写了一篇文章并提供了iPhone 游戏中单例类 IdleTimerManager空闲计时器处理的代码
回答by Mihai Timar
Here is another way to detect activity:
这是检测活动的另一种方法:
The timer is added in UITrackingRunLoopMode
, so it can only fire if there is UITracking
activity. It also has the nice advantage of not spamming you for all touch events, thus informing if there was activity in the last ACTIVITY_DETECT_TIMER_RESOLUTION
seconds. I named the selector keepAlive
as it seems an appropriate use case for this. You can of course do whatever you desire with the information that there was activity recently.
计时器添加在 中UITrackingRunLoopMode
,因此它只能在有UITracking
活动时触发。它还有一个很好的优势,即不会向您发送所有触摸事件的垃圾邮件,从而通知最后ACTIVITY_DETECT_TIMER_RESOLUTION
几秒钟是否有活动。我命名选择器,keepAlive
因为它似乎是一个合适的用例。您当然可以根据最近有活动的信息做任何您想做的事情。
_touchesTimer = [NSTimer timerWithTimeInterval:ACTIVITY_DETECT_TIMER_RESOLUTION
target:self
selector:@selector(keepAlive)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_touchesTimer forMode:UITrackingRunLoopMode];
回答by Jlam
There's a way to do this app wide without individual controllers having to do anything. Just add a gesture recognizer that doesn't cancel touches. This way, all touches will be tracked for the timer, and other touches and gestures aren't affected at all so no one else has to know about it.
有一种方法可以在无需单个控制器执行任何操作的情况下在整个应用程序范围内执行此应用程序。只需添加一个不会取消触摸的手势识别器。这样,计时器将跟踪所有触摸,而其他触摸和手势根本不受影响,因此其他人不必知道它。
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)
}
}
In your app delegate's did finish launch method, just call addGesture and you're all set. All touches will go through the CatchAllGesture's methods without it preventing the functionality of others.
在您的应用程序委托的完成启动方法中,只需调用 addGesture 即可。所有触摸都将通过 CatchAllGesture 的方法,而不会阻止其他人的功能。
回答by wisequark
Ultimately you need to define what you consider to be idle - is idle the result of the user not touching the screen or is it the state of the system if no computing resources are being used? It is possible, in many applications, for the user to be doing something even if not actively interacting with the device through the touch screen. While the user is probably familiar with the concept of the device going to sleep and the notice that it will happen via screen dimming, it is not necessarily the case that they'll expect something to happen if they are idle - you need to be careful about what you would do. But going back to the original statement - if you consider the 1st case to be your definition, there is no really easy way to do this. You'd need to receive each touch event, passing it along on the responder chain as needed while noting the time it was received. That will give you some basis for making the idle calculation. If you consider the second case to be your definition, you can play with an NSPostWhenIdle notification to try and perform your logic at that time.
最终,您需要定义您认为空闲的内容 - 空闲是用户未触摸屏幕的结果,还是没有使用计算资源时的系统状态?在许多应用程序中,即使没有通过触摸屏主动与设备交互,用户也可能正在做某事。虽然用户可能熟悉设备进入睡眠状态的概念以及通过屏幕变暗会发生这种情况的通知,但如果他们处于空闲状态,他们不一定会期望发生某些事情 - 您需要小心关于你会做什么。但是回到最初的陈述 - 如果您认为第一种情况是您的定义,那么没有真正简单的方法可以做到这一点。您需要接收每个触摸事件,根据需要将它传递到响应者链上,同时记录它的接收时间。这将为您提供进行空闲计算的一些基础。如果您认为第二种情况是您的定义,那么您可以使用 NSPostWhenIdle 通知来尝试执行您当时的逻辑。