iOS在非活动期间执行操作(无用户交互)
我如何添加一个计时器到我的iOS应用程序是基于用户交互(或缺乏)? 换句话说,如果2分钟内没有用户交互,我想让应用程序做一些事情,在这种情况下,导航到初始视图控制器。 如果在1:55有人触摸屏幕,定时器重置。 我认为这将需要一个全球定时器,所以无论你在哪个视图上,缺乏交互都会启动定时器。 虽然,我可以在每个视图上创build一个独特的计时器。 有没有人有任何build议,链接或示例代码之前已经完成?
安妮提供的链接是一个很好的起点,但是,作为我,我很难翻译成现有的项目。 我发现了一个博客[原创博客不再存在],它提供了一个更好的一步一步,但它不是为XCode 4.2和使用故事板写的。 以下是我如何获得无活动计时器为我的应用程序工作:
-
创build一个新文件 – > Objective-C类 – >键入一个名称(在我的情况下TIMERUIApplication),并将子类更改为UIApplication。 您可能必须在子类字段中手动input。 您现在应该有适当的.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
-
进入您的支持文件文件夹并将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”),即使没有从该视图导航,相同的视图将在分配的时间之后自行推移。 对我的应用程序来说不是什么大事,但是对于你的应用程序来说可能是。 定时器只有在触摸被识别后才会重置。 如果你想重新回到你想要的页面,重置计时器,请在… pushViewController:controller animated:YES];
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
如果只是坐在那里没有任何交互,这将导致视图每x分钟推动一次。 每次识别触摸时,定时器仍会重置,所以仍然可以工作。
如果您有build议的改进,请发表评论,特别是在当前显示“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]; } }
我仍然是一个nb,可能没有做到最好的方式。 build议总是受欢迎的。
背景[Swift解决scheme]
有一个请求用Swift更新这个答案,所以我在下面添加了一个片段。
请注意,我已经修改了一些规范,为了我自己的用途:我基本上想要做的工作,如果没有UIEvents
5秒。 任何传入的触摸UIEvent
将取消以前的计时器,并重新启动一个新的计时器。
与上面答案的区别
- 从接受的答案上面的一些变化:而不是在第一个事件设置第一个计时器,我立即在
init()
设置我的计时器。 此外,我的reset_idle_timer()
将取消以前的计时器,所以只有一个计时器将随时运行。
重要:build设之前的两个步骤
由于在SO上有几个很好的答案,我能够将上面的代码作为Swift代码进行调整。
-
按照这个答案了解如何在Swift中
UIApplication
。 确保你遵循这些步骤的Swift或下面的代码片段不会编译。 由于链接的答案很好地描述了这些步骤,所以在此不再赘述。 它应该花费你不到一分钟的时间来正确地阅读和设置它。 -
我不能得到
NSTimer
的cancelPreviousPerformRequestsWithTarget:
工作,所以我发现这个更新的GCD解决scheme ,工作很好。 只要把这个代码放到一个单独的.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() } }
我已经实现了Bobbybuild议的,但在Swift中。 代码概述如下。
-
创build一个新文件 – > 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: 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) } }
-
新build一个文件 – > Swift File – > main.swift(这个名字很重要)。
import UIKit UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
-
在您的AppDelegate中: 删除AppDelegate上方的
@UIApplicationMain
。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];
解决这个同样的问题的一个解决办法就是在appdelegate中有一个BOOL,当你想检查用户是否处于空闲状态,并且当你移动到空闲状态时将其设置为false。 然后在idleTimerExceeded方法的TIMERUIApplication中有一个if语句如下。 在你想要检查用户开始空闲的所有视图的viewDidload视图中,将appdelegate.idle设置为true,如果有其他的视图你不需要检查用户是否空闲,你可以将它设置为false 。
-(void)idleTimerExceeded{ AppDelegate *appdelegate = [[UIApplication sharedApplication] delegate]; if(appdelegate.idle){ [[NSNotificationCenter defaultCenter] postNotificationName: kApplicationDidTimeOutNotification object:nil]; } }
Swift 3的例子在这里
-
创build一个类似。
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() } }
-
创build另一个类名为main.swift的粘贴波纹pipe代码
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)) }
-
不要忘记从AppDelegate中删除@UIApplicationMain
-
Swift 3完整的源代码给了GitHub。 GitHub链接: https : //github.com/enamul95/UserInactivity
Swift 3.0 Vanessa答案中的子类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() } } } } }