2008-11-07 14 views
140

Alguien ha implementado una característica en la que si el usuario no ha tocado la pantalla durante un cierto período de tiempo, se realice una acción determinada? Estoy tratando de encontrar la mejor manera de hacerlo.iPhone: La detección de inactividad de usuario/tiempo de inactividad desde el toque última pantalla

Hay un método algo relacionado en UIApplication:

[UIApplication sharedApplication].idleTimerDisabled; 

Sería bueno si en vez tenía algo como esto:

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed; 

Entonces podría configurar un temporizador y comprobar periódicamente este valor, y tome alguna acción cuando exceda un umbral.

Esperemos que explica lo que estoy buscando. ¿Alguien ya ha abordado este tema o tiene alguna idea de cómo lo haría? Gracias.

+0

Esta es una gran pregunta. Windows tiene el concepto de un evento OnIdle, pero creo que se trata más bien de la aplicación que actualmente no maneja nada en su bomba de mensajes en comparación con la propiedad iOS idleTimerDisabled, que parece solo relacionada con el bloqueo del dispositivo. ¿Alguien sabe si hay algo remotamente cercano al concepto de Windows en iOS/MacOSX? – stonedauwg

Respuesta

147

Aquí está la respuesta que había estado buscando:

que su solicitud UIApplication delegado subclase. En el archivo de implementación, anular el sendEvent: Método de este 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"); 
} 

donde MaxIdleTime y idleTimer son variables de instancia.

Para que esto funcione, también necesita modificar su main.m para contar UIApplicationMain utilizar su clase delegado (en este ejemplo, AppDelegate) como la clase principal:

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

Hola Mike, Mi AppDelegate está entrando desde NSObject Así que lo cambié UIApplication e Implementé los métodos anteriores para detectar que el usuario esté inactivo pero recibo el error "Terminar aplicación debido a excepción no detectada 'NSInternalInconsistencyException', razón: 'Solo puede haber una instancia de UIApplication . '"... ¿Hay algo más que deba hacer ...? –

+0

He usado el mismo enfoque, ¡funciona bien! ;) – SlowTree

+6

Agregaría que la subclase UIApplication debería estar separada de la subclase UIApplicationDelegate – boliva

3

En última instancia es necesario definir lo que se considera estar ocioso - está inactivo el resultado de que el usuario no tocar la pantalla o es el estado del sistema si no se utilizan los recursos de computación? Es posible, en muchas aplicaciones, que el usuario haga algo aunque no interactúe activamente con el dispositivo a través de la pantalla táctil. Mientras el usuario está probablemente está familiarizado con el concepto del dispositivo de ir a dormir y el aviso que va a pasar a través de atenuación de la pantalla, no es necesariamente el caso de que ellos esperan que suceda algo si están en reposo - que hay que tener cuidado sobre lo que harías Pero volviendo a la afirmación original: si considera que el primer caso es su definición, no hay una manera realmente fácil de hacerlo. Necesitaría recibir cada evento táctil, pasarlo a lo largo de la cadena de respuesta según sea necesario, al tiempo de anotar la hora en que se recibió. Eso le dará alguna base para hacer el cálculo inactivo. Si considera que el segundo caso es su definición, puede jugar con una notificación NSPostWhenIdle para probar y ejecutar su lógica en ese momento.

+1

Solo para aclarar, estoy hablando de interacción con la pantalla. Actualizaré la pregunta para reflejar eso. –

+1

Luego puede implementar algo donde en cualquier momento que toque un objeto actualice un valor que verifique, o incluso configure (y reinicie) un temporizador inactivo para disparar, pero debe implementarlo usted mismo, porque como dijo wisequark, lo que constituye inactividad varía entre diferentes aplicaciones. –

+1

Estoy definiendo "inactivo" estrictamente como el tiempo desde la última vez que toqué la pantalla. Entiendo que tendré que implementarlo yo mismo, me preguntaba cuál sería la "mejor" manera de, por ejemplo, interceptar toques de pantalla, o si alguien sabe de un método alternativo para determinar esto. –

12

Este hilo fue una gran ayuda, y lo envolví en una subclase UIWindow que envía notificaciones. Elegí las notificaciones para que sea un acoplamiento realmente flexible, pero puede agregar un delegado con la suficiente facilidad.

Aquí está el quid:

http://gist.github.com/365998

Además, la razón de la emisión UIApplication subclase es que la SEMILLA está configurado para crear luego 2 objetos UIApplication ya que contiene la aplicación y el delegado. La subclase UIWindow funciona muy bien.

+0

¡Excelente trabajo por su parte! :) –

+1

¿me puede decir cómo usar su código? No entiendo cómo llamarlo –

+1

Funciona muy bien para toques, pero no parece manejar la entrada del teclado. Esto significa que se agotará el tiempo de espera si el usuario está escribiendo cosas en el teclado de la interfaz gráfica de usuario. –

4

En realidad la idea de subclases funciona muy bien. Simplemente no haga que delegue la subclase UIApplication. Cree otro archivo que herede de UIApplication (por ejemplo, myApp).En IB, establezca la clase del objeto fileOwner en myApp y en myApp.m implemente el método sendEvent como se indicó anteriormente. En main.m do:

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

et voilà!

+1

Sí, la creación de una subclase UIApplication independiente parece funcionar bien. Dejé el segundo parm nil en main. –

+0

@Roby, eche un vistazo que mi consulta http://stackoverflow.com/questions/20088521/calling-method-from-principal-class-in-objective-c. – Tirth

86

Tengo una variación de la solución del temporizador inactivo que no requiere subclases UIApplication. Funciona en una subclase específica UIViewController, por lo que es útil si solo tiene un controlador de vista (como una aplicación interactiva o un juego puede tener) o solo desea manejar el tiempo de espera inactivo en un controlador de vista específico.

Tampoco vuelve a crear el objeto NSTimer cada vez que se restablece el temporizador de inactividad. Solo crea uno nuevo si el temporizador se dispara.

Su código puede llamar al resetIdleTimer para cualquier otro evento que pueda necesitar invalidar el temporizador inactivo (como la entrada de un acelerómetro 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 

(código de limpieza de memoria excluidos por razones de brevedad.)

+0

+1 muy buena respuesta realmente me ayudó – Saawan

+1

Muy bien. ¡Esta respuesta es genial! Supera la respuesta marcada como correcta, aunque sé que fue mucho antes, pero esta es una mejor solución ahora. –

+0

Buena solución. Muchas gracias. – Prazi

4

Me acabo de encontrar con este problema con un juego que está controlado por los movimientos decir tiene bloqueo de la pantalla desactivada, sino permitir de nuevo en el modo de menú. En lugar de un temporizador I encapsulado todas las llamadas a setIdleTimerDisabled dentro de una pequeña clase que proporciona los métodos siguientes:

- (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 desactiva el temporizador de inactividad, enableIdleTimerDelayed al entrar en el menú o lo que debe correr con temporizador de inactividad activa y enableIdleTimer se llama desde su El método applicationWillResignActive de AppDelegate para garantizar que todos sus cambios se restablecen correctamente al comportamiento predeterminado del sistema.
me escribió un artículo y siempre que el código de la clase Singleton IdleTimerManager Idle Timer Handling in iPhone Games

4

Aquí hay otra manera de detectar la actividad:

El temporizador se añade en UITrackingRunLoopMode, por lo que puede sólo el fuego si hay UITracking actividad. También tiene la buena ventaja de no enviarle correos no deseados para todos los eventos táctiles, por lo que informa si hubo actividad en los últimos ACTIVITY_DETECT_TIMER_RESOLUTION segundos. Llamé al selector keepAlive ya que parece un caso de uso apropiado para esto. Por supuesto, puede hacer lo que desee con la información de que hubo actividad recientemente.

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

Este código está incompleto. – Jasper

+0

¿cómo es eso? Creo que está claro que debes hacerte un selector de "keepAlive" para cualquier necesidad que tengas. Tal vez me falta tu punto de vista? –

+0

Usted dice que esta es otra forma de detectar actividad, sin embargo, esto solo instancia un iVar que es un NSTimer. No veo cómo responde esto a la pregunta del OP. – Jasper

6

Para v rápida 3.1

't No se olvide comentar esta línea en 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) 
    } 
} 

crear el archivo main.swif y añadir esta (nombre es importante)

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

Observando la notificación en una cualquiera otra clase

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

¡perfecto! ¡gracias hombre! – BrunoVillanova

0

Todos ellos parecen excesivamente complejo. Creo que esta es una forma más limpia de usar todos los toques que necesita.

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

En su aplicación, los delegados terminaron el método de lanzamiento, simplemente llamen addGesture y ya está todo listo. Todos los toques se realizarán mediante los métodos de CatchAllGesture sin que esto impida la funcionalidad de los demás.

Cuestiones relacionadas