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

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

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

  1. Создайте новый файл -> Objective-C class -> введите имя (в моем случае 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]; 

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

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

Предыстория [Быстрое решение]

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

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

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

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

ВАЖНО: 2 шага перед зданием

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

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

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

ИМХО, приведенный ниже код достаточно прост для понимания. Я заранее извиняюсь за то, что не ответил на любые вопросы по этому вопросу (немного затоплен работой 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() } } 

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

  1. Создайте новый файл -> 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: NSTimeInterval = 5 * 60 var idleTimer: NSTimer? // 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 = NSTimer.scheduledTimerWithTimeInterval(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() { NSNotificationCenter.defaultCenter().postNotificationName(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 .

Примечания. Таймер запускается в любое время при обнаружении касания. Это означает, что если пользователь коснется главного экрана (в моем случае «mainView»), даже не перемещаясь от этого вида, тот же вид будет нажимать на себя после отведенного времени. Не очень важно для моего приложения, но для вас это может быть. Таймер будет сброшен только после распознавания касания. Если вы хотите сбросить таймер, как только вернетесь к странице, на которой хотите быть, включите этот код после … pushViewController: controller animated: 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]; } } 

Swift 3.0 Преобразование подкласса UIApplication в 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() } } } } } 

Пример 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 paste bellow code

     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

Давайте будем гением компьютера.