bottom - status bar ios
iOS realiza una acción después del período de inactividad(sin interacción del usuario) (6)
¿Cómo puedo agregar un temporizador a mi aplicación de iOS que se basa en la interacción del usuario (o la falta de ella)? En otras palabras, si no hay interacción del usuario durante 2 minutos, quiero que la aplicación haga algo, en este caso navegue hasta el controlador de vista inicial. Si a la 1:55 alguien toca la pantalla, el temporizador se restablece. Pensaría que esto necesitaría ser un temporizador global, así que no importa en qué vista estés, la falta de interacción inicia el temporizador. Aunque, podría crear un temporizador único en cada vista. ¿Alguien tiene alguna sugerencia, enlaces o código de muestra donde esto se haya hecho antes?
Notas: El temporizador se iniciará en cualquier momento que se detecte un toque. Esto significa que si el usuario toca la pantalla principal (en mi caso, "mainView") incluso sin navegar fuera de esa vista, la misma vista se presionará después del tiempo asignado. No es un gran problema para mi aplicación, pero para el tuyo podría ser. El temporizador solo se reiniciará una vez que se reconozca un toque. Si desea restablecer el temporizador tan pronto como regrese a la página en la que desea estar, incluya este código después de ... pushViewController: controlador animado: YES];
Una solución a este problema de la misma vista que se muestra de nuevo es tener un BOOL en el delegado de la aplicación y establecerlo en verdadero cuando desee verificar que el usuario esté inactivo y establecerlo en falso cuando se haya movido a la vista inactiva. Luego, en la aplicación TIMERUIA en el método idleTimerExceeded, tenga una instrucción if como la siguiente. En la vista viewDidload de todas las vistas en las que desea verificar si el usuario está inactivo, configura appdelegate.idle como verdadero; si hay otras vistas donde no necesita verificar que el usuario esté inactivo, puede configurar esto como falso .
-(void)idleTimerExceeded{
AppDelegate *appdelegate = [[UIApplication sharedApplication] delegate];
if(appdelegate.idle){
[[NSNotificationCenter defaultCenter] postNotificationName: kApplicationDidTimeOutNotification object:nil];
}
}
Implementé lo que Bobby sugirió, pero en Swift. El código se describe a continuación.
Cree un nuevo archivo -> Swift File -> escriba un nombre (en mi caso, TimerUIApplication) y cambie la subclase a UIApplication. Cambie el archivo TimerUIApplication.swift para que se lea de la siguiente manera:
class TimerUIApplication: UIApplication { static let ApplicationDidTimoutNotification = "AppTimout" // The timeout in seconds for when to fire the idle timer. let timeoutInSeconds: TimeInterval = 5 * 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 event.allTouches?.first(where: { $0.phase == .began }) != nil { resetIdleTimer() } } // Resent the timer because there was user interaction. func resetIdleTimer() { idleTimer?.invalidate() idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(AppDelegate.idleTimerExceeded), userInfo: nil, repeats: false) } // If the timer reaches the limit as defined in timeoutInSeconds, post this notification. func idleTimerExceeded() { Foundation.NotificationCenter.default.post(name: NSNotification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil) } }
Cree un nuevo archivo -> Swift File -> main.swift (el nombre es importante).
import UIKit UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
En su AppDelegate:
@UIApplicationMain
encima de AppDelegate.class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AppDelegate.applicationDidTimout(_:)), name: TimerUIApplication.ApplicationDidTimoutNotification, object: nil) return true } ... // The callback for when the timeout was fired. func applicationDidTimout(notification: NSNotification) { if let vc = self.window?.rootViewController as? UINavigationController { if let myTableViewController = vc.visibleViewController as? MyMainViewController { // Call a function defined in your view controller. myMainViewController.userIdle() } else { // We are not on the main view controller. Here, you could segue to the desired class. let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil) let vc = storyboard.instantiateViewControllerWithIdentifier("myStoryboardIdentifier") } } } }
Tenga en cuenta que puede tener que hacer cosas diferentes en applicationDidTimout dependiendo de su controlador de vista raíz. Consulte esta publicación para obtener más detalles sobre cómo debe lanzar su controlador de vista. Si tiene vistas modales sobre el controlador de navegación, puede usar visibleViewController lugar de topViewController .
Swift 3 ejemplo aquí
crea una clase como.
import Foundation import UIKit 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//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) // print("3") 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 { // print("1") 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() { print("Time Out") NotificationCenter.default.post(name:Notification.Name.TimeOutUserInteraction, object: nil) //Go Main page after 15 second let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.window = UIWindow(frame: UIScreen.main.bounds) let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) let yourVC = mainStoryboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController appDelegate.window?.rootViewController = yourVC appDelegate.window?.makeKeyAndVisible() } }
crear otra clase llamada main.swift paste abajo código
import Foundation import UIKit CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)) { argv in _ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self)) }
no olvides eliminar @UIApplicationMain de AppDelegate
El código fuente completo de Swift 3 se le da a GitHub. Enlace de GitHub: https://github.com/enamul95/UserInactivity
Swift 3.0 Conversión de la aplicación UIApplication
subclasificada en la respuesta de Vanessa
class TimerUIApplication: UIApplication {
static let ApplicationDidTimoutNotification = "AppTimout"
// The timeout in seconds for when to fire the idle timer.
let timeoutInSeconds: TimeInterval = 5 * 60
var idleTimer: Timer?
// 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(TimerUIApplication.idleTimerExceeded), userInfo: nil, repeats: false)
}
// If the timer reaches the limit as defined in timeoutInSeconds, post this notification.
func idleTimerExceeded() {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil)
}
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()
}
}
}
}
}
Antecedentes [Solución Swift]
Hubo una solicitud para actualizar esta respuesta con Swift, así que agregué un fragmento a continuación.
Tenga en cuenta que he modificado un poco las especificaciones para mis propios usos: esencialmente quiero hacer el trabajo si no hay UIEvents
durante 5 segundos. Cualquier toque entrante UIEvent
cancelará los temporizadores anteriores y se reiniciará con un nuevo temporizador.
Diferencias de la respuesta anterior
- Algunos cambios a partir de la respuesta aceptada anteriormente: en lugar de configurar el primer temporizador en el primer evento, configuré mi temporizador en
init()
inmediatamente. Además, mireset_idle_timer()
cancelará el temporizador anterior, por lo que solo se ejecutará un temporizador en cualquier momento.
IMPORTANTE: 2 pasos antes de construir
Gracias a un par de excelentes respuestas sobre SO, pude adaptar el código anterior como código Swift.
Sigue esta respuesta para ver un resumen sobre cómo subclase la aplicación
UIApplication
en Swift. Asegúrese de seguir esos pasos para Swift o no se compilará el siguiente fragmento. Como la respuesta vinculada describió los pasos muy bien, no repetiré aquí. Le tomará menos de un minuto leerlo y configurarlo correctamente.No pude obtener
NSTimer
decancelPreviousPerformRequestsWithTarget:
para trabajar, así que encontré esta solución GCD actualizada que funciona muy bien. Simplemente coloque ese código en un archivo .swift por separado y usted es gtg (para que pueda llamar adelay()
ycancel_delay()
, y utilicedispatch_cancelable_closure
).
En mi humilde opinión, el siguiente código es lo suficientemente simple para que cualquiera lo entienda. Me disculpo de antemano por no contestar ninguna pregunta sobre esta respuesta (un poco inundada con el cajero automático de trabajo).
Acabo de publicar esta respuesta para contribuir de nuevo a SO, qué gran información he obtenido.
Retazo
import UIKit
import Foundation
private let g_secs = 5.0
class MYApplication: UIApplication
{
var idle_timer : dispatch_cancelable_closure?
override init()
{
super.init()
reset_idle_timer()
}
override func sendEvent( event: UIEvent )
{
super.sendEvent( event )
if let all_touches = event.allTouches() {
if ( all_touches.count > 0 ) {
let phase = (all_touches.anyObject() as UITouch).phase
if phase == UITouchPhase.Began {
reset_idle_timer()
}
}
}
}
private func reset_idle_timer()
{
cancel_delay( idle_timer )
idle_timer = delay( g_secs ) { self.idle_timer_exceeded() }
}
func idle_timer_exceeded()
{
println( "Ring ----------------------- Do some Idle Work!" )
reset_idle_timer()
}
}
El enlace que proporcionó Anne fue un gran punto de partida, pero, siendo el n00b que soy, fue difícil traducirlo a mi proyecto existente. Encontré un blog [el blog original ya no existe] que ofrecía un mejor paso a paso, pero no fue escrito para XCode 4.2 y el uso de guiones gráficos. Aquí hay un resumen de cómo obtuve el temporizador de inactividad para que funcione en mi aplicación:
Cree un nuevo archivo -> Objective-C class -> escriba un nombre (en mi caso TIMERUIApplication) y cambie la subclase a UIApplication. Es posible que deba escribirlo manualmente en el campo de la subclase. Ahora debería tener los archivos .h y .m apropiados.
Cambie el archivo .h para que se lea de la siguiente manera:
#import <Foundation/Foundation.h> //the length of time before your application "times out". This number actually represents seconds, so we''ll have to multiple it by 60 in the .m file #define kApplicationTimeoutInMinutes 5 //the notification your AppDelegate needs to watch for in order to know that it has indeed "timed out" #define kApplicationDidTimeoutNotification @"AppTimeOut" @interface TIMERUIApplication : UIApplication { NSTimer *myidleTimer; } -(void)resetIdleTimer; @end
Cambie el archivo .m para que se lea de la siguiente manera:
#import "TIMERUIApplication.h" @implementation TIMERUIApplication //here we are listening for any touch. If the screen receives touch, the timer is reset -(void)sendEvent:(UIEvent *)event { [super sendEvent:event]; if (!myidleTimer) { [self resetIdleTimer]; } NSSet *allTouches = [event allTouches]; if ([allTouches count] > 0) { UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase; if (phase == UITouchPhaseBegan) { [self resetIdleTimer]; } } } //as labeled...reset the timer -(void)resetIdleTimer { if (myidleTimer) { [myidleTimer invalidate]; } //convert the wait period into minutes rather than seconds int timeout = kApplicationTimeoutInMinutes * 60; myidleTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO]; } //if the timer reaches the limit as defined in kApplicationTimeoutInMinutes, post this notification -(void)idleTimerExceeded { [[NSNotificationCenter defaultCenter] postNotificationName:kApplicationDidTimeoutNotification object:nil]; } @end
Vaya a la carpeta Archivos de soporte y modifique main.m a esto (diferente de las versiones anteriores de XCode):
#import <UIKit/UIKit.h> #import "AppDelegate.h" #import "TIMERUIApplication.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, NSStringFromClass([TIMERUIApplication class]), NSStringFromClass([AppDelegate class])); } }
Escriba el código restante en su archivo AppDelegate.m. He omitido el código que no pertenece a este proceso. No hay cambios para hacer en el archivo .h.
#import "AppDelegate.h" #import "TIMERUIApplication.h" @implementation AppDelegate @synthesize window = _window; -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil]; return YES; } -(void)applicationDidTimeout:(NSNotification *) notif { NSLog (@"time exceeded!!"); //This is where storyboarding vs xib files comes in. Whichever view controller you want to revert back to, on your storyboard, make sure it is given the identifier that matches the following code. In my case, "mainView". My storyboard file is called MainStoryboard.storyboard, so make sure your file name matches the storyboardWithName property. UIViewController *controller = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"mainView"]; [(UINavigationController *)self.window.rootViewController pushViewController:controller animated:YES]; }
Notas: El temporizador se iniciará en cualquier momento que se detecte un toque. Esto significa que si el usuario toca la pantalla principal (en mi caso, "mainView") incluso sin navegar fuera de esa vista, la misma vista se presionará después del tiempo asignado. No es un gran problema para mi aplicación, pero para el tuyo podría ser. El temporizador solo se reiniciará una vez que se reconozca un toque. Si desea restablecer el temporizador tan pronto como regrese a la página en la que desea estar, incluya este código después de ... pushViewController: controlador animado: YES];
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
Esto causará que la vista presione cada x minutos si está sentado allí sin interacción. El temporizador se reiniciará cada vez que reconozca un toque, por lo que seguirá funcionando.
Comente si ha sugerido mejoras, especialmente de alguna manera para deshabilitar el temporizador si actualmente se está mostrando "mainView". Parece que no puedo entender mi declaración if para que registre la vista actual. Pero estoy feliz con el lugar donde estoy. Debajo está mi intento inicial en la declaración if para que pueda ver a dónde iba con él.
-(void)applicationDidTimeout:(NSNotification *) notif
{
NSLog (@"time exceeded!!");
UIViewController *controller = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"mainView"];
//I''ve tried a few varieties of the if statement to no avail. Always goes to else.
if ([controller isViewLoaded]) {
NSLog(@"Already there!");
}
else {
NSLog(@"go home");
[(UINavigationController *)self.window.rootViewController pushViewController:controller animated:YES];
//[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
}
}
Todavía soy un n00b y es posible que no haya hecho todo de la mejor manera. Las sugerencias son siempre bienvenidas.