iPhone: Detecting user inactivity/idle time since last screen touch

前端 未结 9 1268
我寻月下人不归
我寻月下人不归 2020-11-22 17:10

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 t

相关标签:
9条回答
  • 2020-11-22 17:19

    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.

    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.

    Your code can call resetIdleTimer for any other events that may need to invalidate the idle timer (such as significant accelerometer 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
    

    (memory cleanup code excluded for brevity.)

    0 讨论(0)
  • 2020-11-22 17:22

    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.

    Here's the gist:

    http://gist.github.com/365998

    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.

    0 讨论(0)
  • 2020-11-22 17:22

    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:

    - (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

    0 讨论(0)
  • 2020-11-22 17:23

    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.

    0 讨论(0)
  • 2020-11-22 17:31

    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.

    _touchesTimer = [NSTimer timerWithTimeInterval:ACTIVITY_DETECT_TIMER_RESOLUTION
                                            target:self
                                          selector:@selector(keepAlive)
                                          userInfo:nil
                                           repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:_touchesTimer forMode:UITrackingRunLoopMode];
    
    0 讨论(0)
  • 2020-11-22 17:33

    For swift v 3.1

    dont't forget comment this line in 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)

    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)
    
    0 讨论(0)
提交回复
热议问题