代码之家  ›  专栏  ›  技术社区  ›  Mike McMaster

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

  •  146
  • Mike McMaster  · 技术社区  · 17 年前

    是否有人实现了一个功能,如果用户在某个时间段内没有触摸屏幕,您会采取某个操作?我在想最好的办法。

    [UIApplication sharedApplication].idleTimerDisabled;
    

    NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;
    

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

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

    9 回复  |  直到 7 年前
        1
  •  155
  •   Mike McMaster    17 年前

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

    让您的应用程序委托子类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");
    }
    

    int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate");
    
        2
  •  88
  •   Chris Miles    15 年前

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

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

        3
  •  23
  •   Sergey Stadnik    9 年前

    对于swift V3.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)
    
        4
  •  12
  •   Brian King    16 年前

    要点如下:

    http://gist.github.com/365998

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

        5
  •  5
  •   zx485 potemkin    10 年前

    实际上,子类化的想法非常有效。只是不要让你的代表成为 UIApplication UIC应用程序 (例如myApp)。在IB中设置 fileOwner 反对 myApp 在myApp.m中实现 sendEvent 方法同上。总的来说,我要:

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

    瞧!

        6
  •  4
  •   Kay    13 年前

    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 方法,以确保将所有更改正确重置为系统默认行为。
    我写了一篇文章,并提供了单例类IdleTimerManager的代码 Idle Timer Handling in iPhone Games

        7
  •  4
  •   Jasper    11 年前

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

    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];
    
        8
  •  4
  •   Jlam    8 年前

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

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

        9
  •  3
  •   wisequark    17 年前

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

        10
  •  0
  •   Dmitriy Mitiai    5 年前

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: 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)