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

时间:2008-11-07 20:02:59

标签: ios objective-c iphone idle-timer

是否有人实施了一项功能,如果用户在一段时间内没有触摸屏幕,您会采取某种行动吗?我正试图找出最佳方法。

UIApplication中有一些与此有关的方法:

[UIApplication sharedApplication].idleTimerDisabled;

如果你有这样的东西,那就太好了:

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

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

希望这能解释我在寻找什么。有没有人已经解决过这个问题,或者对你将如何做到这一点有任何想法?感谢。

10 个答案:

答案 0 :(得分:152)

以下是我一直在寻找的答案:

让您的应用程序委托子类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是实例变量。

为了使其工作,您还需要修改main.m以告诉UIApplicationMain使用您的委托类(在此示例中为AppDelegate)作为主要类:

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

答案 1 :(得分:86)

我有一个空闲计时器解决方案的变体,它不需要子类化UIApplication。它适用于特定的UIViewController子类,因此如果您只有一个视图控制器(如交互式应用程序或游戏可能有)或只想处理特定视图控制器中的空闲超时,则非常有用。

每次重置空闲计时器时,它也不会重新创建NSTimer对象。如果计时器触发,它只会创建一个新的。

您的代码可以针对可能需要使空闲计时器无效的任何其他事件(例如重要的加速计输入)调用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

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

答案 2 :(得分:16)

对于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)
   }
} 
  

创建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))
}

在任何其他班级中观察通知

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

答案 3 :(得分:12)

这个帖子是一个很好的帮助,我将它包装到发送通知的UIWindow子类中。我选择通知使它成为一个真正的松散耦合,但你可以很容易地添加一个委托。

这是要点:

http://gist.github.com/365998

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

答案 4 :(得分:4)

实际上,子类化的想法很有效。只是不要让你的委托成为UIApplication子类。创建另一个继承自UIApplication的文件(例如myApp)。在IB中,将fileOwner对象的类设置为myApp,并在myApp.m中实现上述sendEvent方法。在main.m中:

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

etvoilà!

答案 5 :(得分:4)

我刚刚用一个由动作控制的游戏遇到了这个问题,即禁用了屏幕锁定但在菜单模式下应该再次启用它。我在一个小类中封装了对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,或者在空闲计时器处于活动状态时运行的任何计时器,并且从AppDelegate的enableIdleTimer方法调用applicationWillResignActive以确保所有更改将正确重置为系统默认行为 我写了一篇文章并提供了单例类IdleTimerManager Idle Timer Handling in iPhone Games

的代码

答案 6 :(得分:4)

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

计时器已添加到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];

答案 7 :(得分:3)

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

答案 8 :(得分:3)

有一种方法可以在没有个别控制器必须做任何事情的情况下完成这个应用程序。只需添加一个不取消触摸的手势识别器。这样,所有触摸都将被跟踪以用于计时器,并且其他触摸和手势完全不受影响,因此没有其他人必须知道它。

dup(2)

在你的app appate中完成了启动方法,只需调用addGesture就可以了。所有的触摸都将通过CatchAllGesture的方法,而不会阻止其他人的功能。

答案 9 :(得分:0)

外面是 2021 年,我想分享我在不扩展 UIApplication 的情况下处理这个问题的方法。我不会描述如何创建计时器并重置它。而是如何捕捉所有事件。所以你的 AppDelegate 是这样开始的:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

所以你需要做的就是继承 UIWindow 并覆盖 sendEvent,如下所示

import UIKit

class MyWindow: UIWindow {

    override func sendEvent(_ event: UIEvent){
        super.sendEvent(event)
        NSLog("Application received an event. Do whatever you want")
    }
}

然后用我们的类创建窗口:

self.window = MyWindow(frame: UIScreen.main.bounds)