it-swarm.com.ru

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

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

В UIApplication есть несколько похожий метод:

[UIApplication sharedApplication].idleTimerDisabled;

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

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

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

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

150
Mike McMaster

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

Пусть ваше приложение делегирует подкласс 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");
152
Mike McMaster

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

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

Ваш код может вызывать 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

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

86
Chris Miles

Для Swift 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)
17
Sergey Stadnik

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

Вот суть:

http://Gist.github.com/365998

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

12
Brian King

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

Таймер добавляется в 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];
4
Mihai Timar

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

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

и вуаля!

4
Roby

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

4
Kay

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

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, не мешая функционированию других.

3
Jlam

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

3
wisequark