154

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

UIApplication 中有这个有点相关的方法:

[UIApplication sharedApplication].idleTimerDisabled;

如果你有这样的东西会很好:

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

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

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

4

10 回答 10

156

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

让您的应用程序委托子类 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");
于 2008-11-21T17:20:34.183 回答
88

我有一个空闲计时器解决方案的变体,它不需要子类化 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

(为简洁起见,排除了内存清理代码。)

于 2011-03-11T06:54:03.587 回答
23

对于 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)
于 2017-05-12T14:20:05.207 回答
12

这个线程很有帮助,我将它封装到一个发送通知的 UIWindow 子类中。我选择通知是为了让它成为真正的松耦合,但你可以很容易地添加一个委托。

这是要点:

http://gist.github.com/365998

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

于 2010-04-14T16:14:37.950 回答
5

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

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

等等!

于 2010-08-27T01:16:32.053 回答
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当进入菜单或任何应该在空闲计时器激活的情况下运行并enableIdleTimer从您的 AppDelegate 的applicationWillResignActive方法调用以确保您的所有更改都正确重置为系统默认行为时。
写了一篇文章,提供了iPhone Games 中单例类 IdleTimerManager 空闲定时器处理的代码

于 2013-02-06T08:57:59.993 回答
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];
于 2014-08-13T18:32:28.490 回答
4

有一种方法可以在整个应用程序范围内执行此应用程序,而无需单个控制器执行任何操作。只需添加一个不会取消触摸的手势识别器。这样,所有的触摸都会被计时器跟踪,而其他触摸和手势根本不会受到影响,因此其他人不必知道它。

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 的方法进行,而不会妨碍其他人的功能。

于 2017-08-03T23:19:44.270 回答
3

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

于 2008-11-07T21:15:08.340 回答
1

外面是 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)
于 2021-01-27T12:52:09.393 回答