iOS выполняет действие после периода бездействия (без взаимодействия с пользователем) - PullRequest
53 голосов
/ 10 ноября 2011

Как добавить таймер в мое приложение для iOS, основанное на взаимодействии с пользователем (или его отсутствии)?Другими словами, если в течение 2 минут отсутствует взаимодействие с пользователем, я хочу, чтобы приложение что-то сделало, в этом случае перейдите к начальному контроллеру представления.Если в 1:55 кто-то касается экрана, таймер сбрасывается.Я бы подумал, что это должен быть глобальный таймер, поэтому независимо от того, на каком экране вы находитесь, отсутствие взаимодействия запускает таймер.Хотя я мог бы создать уникальный таймер для каждого представления.У кого-нибудь есть предложения, ссылки или примеры кода, где это было сделано раньше?

Ответы [ 6 ]

115 голосов
/ 25 января 2012

Ссылка, которую предоставила Энн, была отличной отправной точкой, но, поскольку я n00b, мне было трудно перевести мой существующий проект.Я нашел блог [оригинальный блог больше не существует], который давал пошаговое улучшение, но он не был написан для XCode 4.2 и раскадровок.Вот описание того, как я заставил таймер неактивности работать для моего приложения:

  1. Создать новый файл -> класс Objective-C -> ввести имя (в моем случаеTIMERUIApplication) и измените подкласс на UIApplication.Возможно, вам придется вручную ввести это в поле подкласса.Теперь у вас должны быть соответствующие файлы .h и .m.

  2. Измените файл .h следующим образом:

    #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
    
  3. Измените файл .m следующим образом:

    #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
    
  4. Перейдите в папку «Вспомогательные файлы» и измените main.m на эту (в отличие от предыдущих версий 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]));
        }
    }
    
  5. Запишите оставшийся код в файл AppDelegate.m.Я пропустил код, не относящийся к этому процессу.В файле .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];
    }
    

Примечания. Таймер запускается при каждом обнаружении касания.Это означает, что если пользователь прикасается к главному экрану (в моем случае «mainView»), даже не отходя от этого вида, то тот же самый вид перевернется через заданное время.Не имеет большого значения для моего приложения, но для вашего это может быть.Таймер сбрасывается только после распознавания касания.Если вы хотите сбросить таймер, как только вернетесь на страницу, на которой хотите быть, включите этот код после ... pushViewController: controller animated: YES];

[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];

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

Пожалуйста, прокомментируйте, если вы предложили улучшения, особенно если вы хотите отключить таймер, если в данный момент отображается «mainView».Я не могу понять мое заявление if, чтобы заставить его зарегистрировать текущее представление.Но я доволен тем, где я нахожусь.Ниже приведена моя первоначальная попытка утверждения if, чтобы вы могли видеть, куда я шел с ним.

-(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];
    }
}

Я все еще n00b и, возможно, не сделал все наилучшим образом.Предложения всегда приветствуются.

22 голосов
/ 03 марта 2016

Я реализовал то, что предложил Бобби, но в Swift. Код приведен ниже.

  1. Создайте новый файл -> Файл Swift -> введите имя (в моем случае TimerUIApplication) и измените подкласс на UIApplication. + Изменить файл TimerUIApplication.swift следующего содержания:

    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)
        }
    }
    
  2. Создать новый файл -> Swift File -> main.swift (имя важно).

    import UIKit
    
    UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
    
  3. В вашем AppDelegate: Удалить @UIApplicationMain над 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")
                }
            }
        }
    }
    

Имейте в виду, что в applicationDidTimout вам, возможно, придется делать разные вещи в зависимости от вашего корневого контроллера представления. См. этот пост для более подробной информации о том, как вы должны использовать свой контроллер представления. Если у вас есть модальные виды на контроллер навигации, вы можете использовать visibleViewController вместо topViewController .

14 голосов
/ 20 декабря 2014

Фон [Быстрое решение]

Был запрос на обновление этого ответа с помощью Swift, поэтому я добавил фрагмент ниже.

Обратите внимание, что я несколько изменил спецификации для своих собственных нужд: я, по сути, хочу работать, если в течение 5 секунд нет UIEvents. Любое входящее касание UIEvent отменяет предыдущие таймеры и перезапускается с новым таймером.

Отличия от ответа выше

  • Некоторые изменения по сравнению с принятым ответом выше: вместо того, чтобы устанавливать первый таймер при первом событии, я немедленно установил свой таймер в init(). Также мой reset_idle_timer() отменит предыдущий таймер, поэтому в любой момент будет работать только один таймер.

ВАЖНО: 2 шага перед сборкой

Благодаря паре отличных ответов на SO, я смог адаптировать приведенный выше код как код Swift.

  • Следуйте этому ответу , чтобы получить краткое изложение того, как подкласс UIApplication в Swift. Убедитесь, что вы выполнили эти шаги для Swift, иначе фрагмент ниже не скомпилируется. Поскольку связанный ответ так хорошо описал шаги, я не буду здесь повторяться. Это займет у вас меньше минуты, чтобы прочитать и правильно настроить его.

  • Мне не удалось заставить NSTimer cancelPreviousPerformRequestsWithTarget: работать, поэтому я нашел это обновленное решение GCD , которое прекрасно работает. Просто поместите этот код в отдельный файл .swift, и вы получите gtg (чтобы вы могли звонить delay() и cancel_delay() и использовать dispatch_cancelable_closure).

ИМХО, приведенный ниже код достаточно прост для понимания. Заранее прошу прощения за то, что не ответил ни на один вопрос по этому ответу (немного затоплен работой атм).

Я только что опубликовал этот ответ, чтобы сообщить СО, какую прекрасную информацию я получил.

Отрывок

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()
    }
}
4 голосов
/ 16 августа 2017

Swift 3 пример здесь

  1. создать класс как.

     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()
    
    
       }
    }
    
  2. создайте другой класс с именем main.swift вставьте код ниже

    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))
            }
    
  3. не забудьте удалить @ UIApplicationMain из AppDelegate

  4. Полный исходный код Swift 3 передается GitHub. Ссылка на GitHub: https://github.com/enamul95/UserInactivity

4 голосов
/ 24 января 2013

Примечания. Таймер запускается при каждом обнаружении касания.Это означает, что если пользователь прикасается к главному экрану (в моем случае «mainView»), даже не отходя от этого вида, то тот же самый вид перевернется через заданное время.Не имеет большого значения для моего приложения, но для вашего это может быть.Таймер сбрасывается только после распознавания касания.Если вы хотите сбросить таймер, как только вернетесь на страницу, на которой хотите быть, включите этот код после ... pushViewController: controller animated: YES];

Одно решение дляэта проблема того же самого начала, отображаемого снова, заключается в том, чтобы в приложении applelegate был указан BOOL и установить его в значение true, если вы хотите проверить, не занят ли пользователь, и установить в значение false, когда вы перешли в режим ожидания.Затем в приложении TIMERUIApplication в методе idleTimerExceeded есть оператор if, как показано ниже.В представлении viewDidload всех представлений, в которых вы хотите проверить, не начал ли пользователь работать, вы устанавливаете appdelegate.idle в true, если есть другие представления, где вам не нужно проверять, находится ли пользователь в режиме ожидания, вы можете установить в false.

-(void)idleTimerExceeded{
          AppDelegate *appdelegate = [[UIApplication sharedApplication] delegate];

          if(appdelegate.idle){
            [[NSNotificationCenter defaultCenter] postNotificationName: kApplicationDidTimeOutNotification object:nil]; 
          }
}
2 голосов
/ 29 марта 2017

Swift 3.0 Преобразование подкласса UIApplication в ответе Ванессы

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

    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...