iPhone: wykrywanie nieaktywności użytkownika / czasu bezczynności od ostatniego dotknięcia ekranu

Czy ktoś zaimplementował funkcję, w której jeśli użytkownik nie dotknął ekranu przez określony czas, podejmujesz określone działanie? Próbuję znaleźć najlepszy sposób, żeby to zrobić.

W UIApplication znajduje się ta nieco powiązana Metoda:

[UIApplication sharedApplication].idleTimerDisabled;

Byłoby miło gdybyś zamiast tego miał coś takiego:

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

Następnie mogę ustawić timer i okresowo sprawdzać tę wartość, i podjąć pewne działania, gdy przekroczy próg.

Miejmy nadzieję, że to wyjaśnia to czego szukam. Czy ktoś już zajął się tym problemem lub ma jakieś przemyślenia na temat tego, jak byś to zrobił? Dzięki.

Author: Mike McMaster, 2008-11-07

9 answers

Oto odpowiedź, której szukałem:

Niech Twoja aplikacja przekaże podklasę UIApplication. W pliku implementacji nadpisuje metodę sendEvent: w następujący sposób:

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

Gdzie maxIdleTime i idleTimer są zmiennymi instancji.

Aby to zadziałało, musisz również zmodyfikować swój główny.m aby powiedzieć UIApplicationMain, aby używała klasy delegata (w tym przykładzie AppDelegate) jako głównej klasy:

int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate");
 152
Author: Mike McMaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 17:20:34

Mam odmianę rozwiązania idle timer, która nie wymaga podklasowania aplikacji. Działa on na określonej podklasie UIViewController, więc jest przydatny, jeśli masz tylko jeden kontroler widoku (np. interaktywną aplikację lub grę) lub chcesz obsłużyć tylko limit czasu bezczynności w określonym kontrolerze widoku.

Nie tworzy również ponownie obiektu NSTimer za każdym razem, gdy wyzerowany jest timer bezczynności. Tworzy nowy tylko wtedy, gdy timer zostanie wywołany.

Twój kod może wywołać resetIdleTimer dla dowolnego inne zdarzenia, które mogą wymagać unieważnienia zegara bezczynności (np. znaczące wejście akcelerometru).

@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

(Kod czyszczenia pamięci wykluczony ze względu na zwięzłość.)

 86
Author: Chris Miles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-11 06:54:03

Ten wątek był bardzo pomocny i owinąłem go w podklasę UIWindow, która wysyła powiadomienia. Wybrałem powiadomienia, aby było to prawdziwe luźne połączenie, ale możesz łatwo dodać delegata.

Oto sedno sprawy:

Http://gist.github.com/365998

Również powodem problemu z podklasą UIApplication jest to, że NIB jest ustawiony, aby następnie utworzyć 2 Obiekty UIApplication, ponieważ zawiera aplikację i delegata. Uiwindow subclass works ale świetnie.

 12
Author: Brian King,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-14 16:14:37

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

Utwórz main.plik swif i dodaj to (nazwa jest ważna)

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

Obserwacja w dowolnej innej klasie

NotificationCenter.default.addObserver(self, selector: #selector(someFuncitonName), name: Notification.Name.TimeOutUserInteraction, object: nil)
 9
Author: Sergey Stadnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-12 14:27:21

Właśnie natknąłem się na ten problem z grą, która jest kontrolowana ruchami, tzn. ma wyłączoną blokadę ekranu, ale powinna włączyć ją ponownie, gdy jest w trybie menu. Zamiast timera zamknąłem wszystkie wywołania setIdleTimerDisabled w małej klasie dostarczającej następujące metody:

- (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 wyłącza bezczynny timer, enableIdleTimerDelayed po wejściu do menu lub cokolwiek powinno działać z bezczynnym timerem aktywnym i enableIdleTimer jest wywoływany z metody AppDelegate applicationWillResignActive, aby upewnić się, że wszystkie zmiany są resetowane poprawnie do domyślne zachowanie systemu.
Napisałam artykuł i podałam kod dla klasy singleton IdleTimerManager obsługa bezczynnego timera w grach na iPhone ' a

 4
Author: Kay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-06 08:57:59

Oto inny sposób na wykrycie aktywności:

Timer jest dodawany w UITrackingRunLoopMode, więc może strzelać tylko wtedy, gdy istnieje UITracking aktywność. Ma również tę zaletę, że nie spamuje cię za wszystkie zdarzenia dotykowe, informując w ten sposób, czy była aktywność w ciągu ostatnich ACTIVITY_DETECT_TIMER_RESOLUTION sekund. Nazwałem selektor keepAlive, ponieważ wydaje się to odpowiednim przypadkiem użycia. Możesz oczywiście robić, co chcesz, z informacją, że ostatnio była aktywność.

_touchesTimer = [NSTimer timerWithTimeInterval:ACTIVITY_DETECT_TIMER_RESOLUTION
                                        target:self
                                      selector:@selector(keepAlive)
                                      userInfo:nil
                                       repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_touchesTimer forMode:UITrackingRunLoopMode];
 4
Author: Mihai Timar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-10 12:42:00

Właściwie pomysł podklasowania działa świetnie. Tylko nie rób z delegata podklasy UIApplication. Utwórz inny plik dziedziczący po UIApplication (np. myApp). W IB Ustaw klasę obiektu {[3] } na myApp, a w myApp.m zaimplementować metodę sendEvent Jak wyżej. In main.m do:

int retVal = UIApplicationMain(argc,argv,@"myApp.m",@"myApp.m")
Et voilà!
 4
Author: Roby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-30 18:24:53

Ostatecznie musisz zdefiniować to, co uważasz za bezczynne - czy bezczynność jest wynikiem nie dotykania ekranu przez użytkownika, czy jest to stan systemu, jeśli nie są używane zasoby obliczeniowe? W wielu aplikacjach możliwe jest, że użytkownik robi coś, nawet jeśli nie wchodzi aktywnie w interakcję z urządzeniem za pośrednictwem ekranu dotykowego. Chociaż użytkownik jest prawdopodobnie zaznajomiony z koncepcją uśpienia urządzenia i informacją, że nastąpi to poprzez przyciemnianie ekranu, nie jest to koniecznie przypadku, że będą oczekiwać, że coś się stanie, jeśli są bezczynni - trzeba być ostrożnym o to, co byś zrobił. Ale wracając do oryginalnego stwierdzenia-jeśli uważasz, że pierwszy przypadek jest twoją definicją, nie ma naprawdę łatwego sposobu, aby to zrobić. Musisz odbierać każde zdarzenie dotykowe, przekazując je w razie potrzeby na łańcuchu odpowiedzi, odnotowując czas jego otrzymania. To da Ci podstawy do bezczynnych obliczeń. Jeśli uznasz drugi przypadek za twoja definicja, możesz grać z powiadomieniem NSPostWhenIdle, aby spróbować wykonać swoją logikę w tym czasie.

 3
Author: wisequark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-07 21:15:08

Jest sposób, aby zrobić tę aplikację szeroko bez indywidualnych kontrolerów konieczności cokolwiek zrobić. Wystarczy dodać rozpoznawanie gestów, które nie anuluje dotknięć. W ten sposób wszystkie dotknięcia będą śledzone dla timera, a inne dotknięcia i gesty nie są w ogóle naruszone, więc nikt inny nie musi o tym wiedzieć.

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

W aplikacji delegat ' s did finish launch method, wystarczy wywołać addGesture i wszystko gotowe. Wszystkie dotknięcia przejdą przez metody CatchAllGesture bez zapobiegania funkcjonalność innych.

 1
Author: Jlam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 04:10:41