iPhone: rilevamento di inattività / tempo di inattività dell’utente dall’ultimo touch sullo schermo

Qualcuno ha implementato una funzione in cui se l’utente non ha toccato lo schermo per un certo periodo di tempo, si intraprende una determinata azione? Sto cercando di capire il modo migliore per farlo.

C’è questo metodo un po ‘correlato in UIApplication:

[UIApplication sharedApplication].idleTimerDisabled; 

Sarebbe bello se tu avessi invece qualcosa del genere:

 NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed; 

Quindi potrei impostare un timer e controllare periodicamente questo valore, e prendere qualche azione quando supera una soglia.

Spero che questo spieghi cosa sto cercando. Qualcuno ha già affrontato questo problema, o ha qualche idea su come lo faresti? Grazie.

Ecco la risposta che stavo cercando:

Chiedi alla tua applicazione di debind l’applicazione UIA di sottoclass. Nel file di implementazione, sovrascrivi il metodo sendEvent: in questo modo:

 - (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"); } 

dove maxIdleTime e idleTimer sono variabili di istanza.

Affinché ciò funzioni, è necessario modificare anche il file main.m per comunicare a UIApplicationMain di utilizzare la class delegate (in questo esempio, AppDelegate) come class principale:

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

Ho una variazione della soluzione del timer di inattività che non richiede la sottoclass dell’applicazione UIA. Funziona su una sottoclass UIViewController specifica, quindi è utile se si ha solo un controller di visualizzazione (come può avere un’applicazione o un gioco interattivo) o si desidera solo gestire il timeout di inattività in uno specifico controller di visualizzazione.

Inoltre, non ricrea l’object NSTimer ogni volta che viene ripristinato il timer di inattività. Ne crea uno nuovo solo se il timer scatta.

Il tuo codice può chiamare resetIdleTimer per tutti gli altri eventi che potrebbero aver bisogno di invalidare il timer di inattività (come l’input accelerometro significativo).

 @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 

(codice di pulizia della memoria escluso per brevità.)

Questo thread è stato di grande aiuto e l’ho incluso in una sottoclass UIWindow che invia notifiche. Ho scelto le notifiche per renderlo un vero accoppiamento, ma puoi aggiungere un delegato abbastanza facilmente.

Ecco il succo:

http://gist.github.com/365998

Inoltre, il motivo del problema della sottoclass UIApplication è che il NIB è configurato per creare quindi 2 oggetti UIApplication poiché contiene l’applicazione e il delegato. La sottoclass UIWindow funziona benissimo.

Per swift v 3.1

non dimenticare di commentare questa riga 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) } } 

crea il file main.swif e aggiungi questo (il nome è importante)

 CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer.self, capacity: Int(CommandLine.argc)) {argv in _ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self)) } 

Osservando la notifica in qualsiasi altra class

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

Mi sono imbattuto in questo problema con un gioco controllato da movimenti, vale a dire che il blocco dello schermo è disabilitato ma dovrebbe triggersrlo di nuovo quando si è in modalità menu. Invece di un timer ho incapsulato tutte le chiamate a setIdleTimerDisabled all’interno di una piccola class che fornisce i seguenti metodi:

 - (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 distriggers il timer di inattività, enableIdleTimerDelayed quando si accede al menu o qualunque cosa deve essere eseguito con il timer di inattività attivo e enableIdleTimer viene chiamato dal metodo applicationWillResignActive di AppDelegate per garantire che tutte le modifiche vengano ripristinate correttamente al comportamento predefinito del sistema.
Ho scritto un articolo e fornito il codice per la class singleton IdleTimerManager Idle Timer Handling in iPhone Games

Ecco un altro modo per rilevare l’attività:

Il timer viene aggiunto in UITrackingRunLoopMode , quindi può essere UITrackingRunLoopMode solo se esiste un’attività UITracking . Ha anche il vantaggio di non spammare per tutti gli eventi touch, informando quindi se ci fosse attività negli ultimi secondi ACTIVITY_DETECT_TIMER_RESOLUTION . Ho chiamato il selettore keepAlive in quanto sembra un caso d’uso appropriato per questo. Ovviamente puoi fare tutto ciò che desideri con le informazioni che ci sono state attività di recente.

 _touchesTimer = [NSTimer timerWithTimeInterval:ACTIVITY_DETECT_TIMER_RESOLUTION target:self selector:@selector(keepAlive) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:_touchesTimer forMode:UITrackingRunLoopMode]; 

In realtà l’idea di sottoclass funziona alla grande. Non debind la sottoclass UIApplication al proprio delegato. Crea un altro file che eredita dall’applicazione UIApplication (ad esempio myApp). In IB impostare la class dell’object fileOwner su myApp e in myApp.m implementare il metodo sendEvent come sopra. In main.m do:

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

et voilà!

In definitiva, devi definire ciò che consideri inattivo: è inattivo il risultato che l’utente non tocca lo schermo o è lo stato del sistema se non vengono utilizzate risorse di elaborazione? In molte applicazioni è ansible che l’utente stia facendo qualcosa anche se non interagisce triggersmente con il dispositivo attraverso il touch screen. Mentre l’utente ha probabilmente familiarità con il concetto di dispositivo che va a dormire e l’avviso che avverrà tramite oscuramento dello schermo, non è necessariamente il caso che si aspettino che accada qualcosa se sono inattivi – è necessario stare attenti su cosa vorresti fare Ma tornando alla dichiarazione originale – se consideri il primo caso come la tua definizione, non c’è un modo veramente semplice per farlo. Dovresti ricevere ogni evento di touch, passandolo lungo la catena di risposta, se necessario, mentre osservi il momento in cui è stato ricevuto. Questo ti darà una base per fare il calcolo del minimo. Se consideri che il secondo caso sia la tua definizione, puoi giocare con una notifica NSPostWhenIdle per provare ed eseguire la tua logica in quel momento.

Questi sembrano tutti eccessivamente complessi. Credo che questo sia un modo più pulito per debind tutti i tocchi di cui hai bisogno.

 fileprivate var timer ... //timer logic here @objc public class CatchAllGesture : UIGestureRecognizer { override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) } override public func touchesEnded(_ touches: Set, with event: UIEvent) { //reset your timer here state = .failed super.touchesEnded(touches, with: event) } override public func touchesMoved(_ touches: Set, 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) } } 

Nella tua app, il delegato ha terminato il metodo di lancio, basta chiamare addGesture e tutto è pronto. Tutti i tocchi passeranno attraverso i metodi di CatchAllGesture senza impedire la funzionalità degli altri.