IPhone: обнаружение бездействия пользователя/время простоя с момента последнего касания экрана

Кто-нибудь реализовал функцию, в которой, если пользователь не коснулся экрана в течение определенного периода времени, вы предпринимаете определенное действие? Я пытаюсь найти лучший способ сделать это.

Вот этот несколько-связанный метод в UIApplication:

[UIApplication sharedApplication].idleTimerDisabled;

Было бы неплохо, если бы у вас было что-то вроде этого:

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

Затем я мог настроить таймер и периодически проверять это значение и предпринимать некоторые действия, когда он превышает пороговое значение.

Надеюсь, это объясняет, что я ищу. Кто-нибудь решился на эту проблему или подумал о том, как вы это сделаете? Спасибо.

Ответ 1

Вот ответ, который я искал:

Попросите делегировать приложение подкласса UIApplication. В файле реализации переопределите метод sendEvent:

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

где maxIdleTime и idleTimer являются переменными экземпляра.

Чтобы это сработало, вам также нужно изменить свой main.m, чтобы сообщить UIApplicationMain, чтобы использовать ваш класс делегата (в этом примере AppDelegate) в качестве основного класса:

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

Ответ 2

У меня есть вариант решения таймера бездействия, который не требует подкласса UIApplication. Он работает с определенным подклассом UIViewController, поэтому полезно, если у вас есть только один контроллер представления (например, интерактивное приложение или игра) или только хотите обрабатывать тайм-аут простоя в определенном контроллере представления.

Он также не воссоздает объект NSTimer каждый раз, когда таймер простоя reset. Он создает только новый, если срабатывает таймер.

Ваш код может вызывать resetIdleTimer для любых других событий, которые могут потребовать аннулирования таймера простоя (например, значительного входа акселерометра).

@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

(для краткости исключен код очистки памяти.)

Ответ 3

Для быстрого v 3.1

Не забывайте прокомментировать эту строку в 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)
   }
} 

создайте файл main.swif и добавьте это (имя важно)

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

Наблюдение уведомления в любом другом классе

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

Ответ 4

Этот поток был большой помощью, и я завернул его в подкласс UIWindow, который отправляет уведомления. Я выбрал уведомления, чтобы сделать его действительно свободным, но вы можете добавить делегата достаточно легко.

Здесь суть:

http://gist.github.com/365998

Кроме того, причиной проблемы подкласса UIApplication является то, что NIB настроен для создания двух объектов UIApplication, поскольку он содержит приложение и делегат. Подкласс UIWindow отлично работает.

Ответ 5

На самом деле идея подкласса отлично работает. Просто не делайте делегатом подкласс UIApplication. Создайте еще один файл, который наследуется от UIApplication (например, myApp). В IB задайте класс объекта fileOwner для myApp, а в myApp.m реализуем метод sendEvent, как указано выше. В main.m do:

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

et voilà!

Ответ 6

Я просто столкнулся с этой проблемой с игрой, которая контролируется движениями, то есть отключена блокировка экрана, но должна снова включить ее в режиме меню. Вместо таймера я инкапсулировал все вызовы setIdleTimerDisabled в малый класс, предоставляя следующие методы:

- (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 деактивирует таймер простоя, enableIdleTimerDelayed при входе в меню или что-то, что должно запускаться с активным таймером ожидания, и enableIdleTimer вызывается из вашего метода AppDelegate applicationWillResignActive, чтобы все ваши изменения были reset поведение по умолчанию системы.
Я написал статью и предоставил код для одноэлементного класса IdleTimerManager Idle Timer Handling в iPhone Games

Ответ 7

Вот еще один способ обнаружить активность:

Таймер добавляется в UITrackingRunLoopMode, поэтому он может срабатывать только при наличии UITracking активности. Он также имеет приятное преимущество не спамить вас для всех событий касания, тем самым сообщая, была ли активность за последние ACTIVITY_DETECT_TIMER_RESOLUTION секунды. Я назвал селектор keepAlive, поскольку для этого он подходит. Вы, конечно, можете делать все, что захотите, с информацией о том, что в последнее время была активность.

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

Ответ 8

В конечном счете вам нужно определить, что вы считаете праздным, - это недействительный результат, когда пользователь не касается экрана или это состояние системы, если не используются вычислительные ресурсы? Возможно, во многих приложениях пользователь может что-то делать, даже если он не активно взаимодействует с устройством через сенсорный экран. Хотя пользователь, вероятно, знаком с концепцией спящего устройства и уведомлением о том, что это произойдет через затемнение экрана, не обязательно, чтобы они ожидали, что что-то произойдет, если они простаивают - вам нужно быть осторожными о том, что вы будете делать. Но вернемся к первоначальному утверждению - если вы считаете, что 1-й случай является вашим определением, нет действительно простого способа сделать это. Вам нужно будет получить каждое событие касания, передавая его по цепочке ответчиков по мере необходимости, отмечая время его получения. Это даст вам основание для расчета простоя. Если вы считаете, что второй случай является вашим определением, вы можете играть с уведомлением NSPostWhenIdle, чтобы попытаться выполнить свою логику в то время.

Ответ 9

Есть способ сделать это приложение широким без каких-либо действий отдельных контроллеров. Просто добавьте распознаватель жестов, который не отменяет прикосновения. Таким образом, все прикосновения будут отслеживаться по таймеру, а другие прикосновения и жесты не будут затронуты вообще, поэтому никто больше не должен знать об этом.

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

В вашем приложении делегат закончил запуск метода, просто вызовите addGesture, и все готово. Все касания будут проходить через методы CatchAllGesture без ущерба для функциональности других.