IOS выполняет действие после периода бездействия (без взаимодействия с пользователем) - программирование
Подтвердить что ты не робот

IOS выполняет действие после периода бездействия (без взаимодействия с пользователем)

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

4b9b3361

Ответ 1

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

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

  • Измените файл .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
    
  • Измените файл .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
    
  • Перейдите в папку Supporting Files и измените 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]));
        }
    }
    
  • Напишите оставшийся код в файле 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" ), даже не перемещаясь от этого вида, тот же вид будет нажимать на себя после отведенного времени. Не очень важно для моего приложения, но для вас это может быть. Таймер будет только reset после распознавания касания. Если вы хотите reset таймер, как только вы вернетесь на страницу, на которой хотите быть, включите этот код после... pushViewController: controller animated: YES];

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

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

Прокомментируйте, если вы предложили улучшения, особенно когда-нибудь, чтобы отключить таймер, если в настоящее время отображается "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 и, возможно, не сделал всего наилучшего. Предложения всегда приветствуются.

Ответ 2

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

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

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

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

ВАЖНО: 2 шага до здания

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

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

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

IMHO, приведенный ниже код достаточно прост для понимания. Я заранее извиняюсь за то, что не ответил на любые вопросы по этому вопросу (немного затоплен работой atm).

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

Отрывок

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

Ответ 3

Я реализовал то, что предложил Бобби, но в Свифте. Ниже приведен код.

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

    import UIKit
    
    UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
    
  • В приложении 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.

Ответ 4

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

Одно из решений этой проблемы с тем же видом начала отображения, которое отображается снова, - это иметь BOOL в appdelegate и установить для него значение 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]; 
          }
}

Ответ 5

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

  • создать класс вроде.

     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()
    
    
       }
    }
    
  • создать другой класс с именем 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))
            }
    
  • не забудьте удалить @UIApplicationMain из AppDelegate

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

Ответ 6

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

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

    }
}