Quelqu'un a-t-il mis en œuvre une fonctionnalité dans laquelle, si l'utilisateur n'a pas touché l'écran pendant un certain temps, vous prenez une certaine mesure? J'essaie de trouver le meilleur moyen de le faire.
Il y a cette méthode quelque peu liée dans UIApplication:
[UIApplication sharedApplication].idleTimerDisabled;
Ce serait bien si vous aviez plutôt quelque chose comme ça:
NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;
Ensuite, je pourrais configurer une minuterie et vérifier périodiquement cette valeur et prendre des mesures lorsqu'elle dépasse un seuil.
J'espère que cela explique ce que je recherche. Quelqu'un a-t-il déjà abordé ce problème ou a-t-il une idée de la façon dont vous le feriez? Merci.
Voici la réponse que je cherchais:
Demandez à votre application de déléguer la sous-classe UIApplication. Dans le fichier d'implémentation, substituez la méthode sendEvent: comme suit:
- (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");
}
où maxIdleTime et idleTimer sont des variables d'instance.
Pour que cela fonctionne, vous devez également modifier votre main.m pour indiquer à UIApplicationMain d'utiliser votre classe delegate (dans cet exemple, AppDelegate) en tant que classe principale:
int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate");
J'ai une variante de la solution de minuterie inactive qui ne nécessite pas de sous-classe UIApplication. Il fonctionne sur une sous-classe UIViewController spécifique. Il est donc utile si vous ne possédez qu'un seul contrôleur de vue (comme peut l'être une application ou un jeu interactif) ou si vous souhaitez uniquement gérer le délai d'inactivité dans un contrôleur de vue spécifique.
En outre, il ne recrée pas l'objet NSTimer chaque fois que le minuteur inactif est réinitialisé. Il en crée un nouveau uniquement si la minuterie se déclenche.
Votre code peut appeler resetIdleTimer
pour tout autre événement susceptible d'invalider le minuteur inactif (tel qu'une entrée d'accéléromètre significative).
@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
(code de nettoyage de la mémoire exclu pour des raisons de brièveté.)
Pour Swift v 3.1
n'oubliez pas de commenter cette ligne dans 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)
}
}
créer un fichier main.swif et l'ajouter (le nom est important)
CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)) {argv in
_ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self))
}
Observer une notification dans une autre classe
NotificationCenter.default.addObserver(self, selector: #selector(someFuncitonName), name: Notification.Name.TimeOutUserInteraction, object: nil)
Ce fil de discussion a été d'une grande aide et je l'ai résumé dans une sous-classe UIWindow qui envoie des notifications. J'ai choisi les notifications pour en faire un couplage lâche, mais vous pouvez facilement ajouter un délégué.
Voici le Gist:
De plus, le problème de la sous-classe UIApplication tient au fait que la carte NIB est configurée pour créer ensuite 2 objets UIApplication, car elle contient l'application et le délégué. La sous-classe UIWindow fonctionne très bien.
Voici un autre moyen de détecter une activité:
La minuterie est ajoutée dans UITrackingRunLoopMode
, elle ne peut donc être déclenchée que s'il y a une activité UITracking
. Il a également l’avantage de ne pas vous spammer pour tous les événements tactiles, ce qui permet de savoir s’il y a eu une activité au cours de la dernière ACTIVITY_DETECT_TIMER_RESOLUTION
secondes. J'ai nommé le sélecteur keepAlive
car il semble que ce soit un cas d'utilisation approprié. Vous pouvez bien sûr faire ce que vous désirez avec l'information qu'il y a eu de l'activité récemment.
_touchesTimer = [NSTimer timerWithTimeInterval:ACTIVITY_DETECT_TIMER_RESOLUTION
target:self
selector:@selector(keepAlive)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_touchesTimer forMode:UITrackingRunLoopMode];
En fait, l'idée de sous-classes fonctionne très bien. Juste, ne faites pas de votre délégué la sous-classe UIApplication
. Créez un autre fichier qui hérite de UIApplication
(par exemple, monApp). Dans IB, définissez la classe de l'objet fileOwner
sur myApp
et dans myApp.m, implémentez la méthode sendEvent
comme ci-dessus. En main.m faire:
int retVal = UIApplicationMain(argc,argv,@"myApp.m",@"myApp.m")
et voilà!
Je viens de rencontrer ce problème avec un jeu contrôlé par des mouvements, c’est-à-dire que le verrouillage de l’écran est désactivé mais qu’il doit être réactivé en mode menu. Au lieu d'une minuterie, j'ai encapsulé tous les appels à setIdleTimerDisabled
dans une petite classe en fournissant les méthodes suivantes:
- (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
désactive le minuteur inactif, enableIdleTimerDelayed
lorsque vous entrez dans le menu ou quoi que ce soit qui doit fonctionner avec un minuteur inactif actif et enableIdleTimer
est appelé à partir de la méthode _ de votre AppDelegate applicationWillResignActive
vos modifications sont réinitialisées correctement au comportement par défaut du système.
J'ai écrit un article et fourni le code de la classe singleton IdleTimerManager Gestion de la minuterie d'inactivité dans les jeux iPhone
Il existe un moyen de faire cette application large sans que les contrôleurs individuels aient à faire quoi que ce soit. Ajoutez simplement un identificateur de geste qui n'annule pas les touches. De cette façon, toutes les touches seront suivies pour le chronomètre, et les autres touches et gestes ne seront pas affectés du tout, de sorte que personne d'autre n'a à le savoir.
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)
}
}
Dans la méthode de lancement du délégué de votre application, appelez simplement addGesture et vous êtes prêt. Toutes les touches vont passer par les méthodes de CatchAllGesture sans que cela empêche la fonctionnalité des autres.
En fin de compte, vous devez définir ce que vous considérez comme étant inactif: l’inactivité est-elle due au fait que l’utilisateur ne touche pas l’écran ou s’agit-il de l’état du système si aucune ressource informatique n’est utilisée? Dans de nombreuses applications, il est possible que l'utilisateur fasse quelque chose même s'il n'interagit pas activement avec l'appareil via l'écran tactile. Bien que l'utilisateur connaisse probablement le principe de mise en veille de l'appareil et la notification que cela se produira via la gradation de l'écran, il n'est pas forcément certain qu'ils s'attendent à ce qu'il se passe quelque chose s'ils restent inactifs - vous devez faire attention. à propos de ce que vous feriez. Mais revenons à la déclaration initiale - si vous considérez le premier cas comme votre définition, il n’ya pas de moyen très simple de le faire. Vous devez recevoir chaque événement tactile en le transmettant au besoin à la chaîne de répondeurs tout en notant l'heure à laquelle il a été reçu. Cela vous donnera une base pour effectuer le calcul inactif. Si vous considérez que le deuxième cas est votre définition, vous pouvez jouer avec une notification NSPostWhenIdle pour essayer d'exécuter votre logique à ce moment-là.