如何在基于用户交互(或缺少用户交互)的iOS应用程序中添加计时器?换句话说,如果2分钟内没有用户交互,我想让应用程序执行某些操作,在这种情况下导航到初始视图控制器.如果在1:55有人触摸屏幕,则计时器重置.我认为这需要一个全局计时器,所以无论你在哪个视图,缺乏交互启动计时器.虽然,我可以在每个视图上创建一个独特的计时器.有没有人有任何建议,链接或示例代码,以前这样做过?
Bob*_*con 118
Anne提供的链接是一个很好的起点,但是,作为我的n00b,很难转化为我现有的项目.我发现一个博客[原始博客不再存在]提供了更好的一步一步,但它不是为XCode 4.2和使用故事板编写的.以下是我如何让不活动计时器适用于我的应用程序:
创建一个新文件 - > Objective-C类 - >键入一个名称(在我的例子中是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
Run Code Online (Sandbox Code Playgroud)将.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 || phase == UITouchPhaseMoved)
{
[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
Run Code Online (Sandbox Code Playgroud)进入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]));
}
}
Run Code Online (Sandbox Code Playgroud)在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];
}
Run Code Online (Sandbox Code Playgroud)注意:只要检测到触摸,计时器就会启动.这意味着如果用户触摸主屏幕(在我的情况下为"mainView"),即使没有离开该视图,相同的视图也会在分配的时间后自行移动.对我的应用程序来说没什么大不了的,但对你的应用来说可能就是这样.只有在识别到触摸时,计时器才会重置.如果要在返回到想要的页面后立即重置计时器,请在...之后包含此代码... pushViewController:controller animated:YES];
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
Run Code Online (Sandbox Code Playgroud)
如果视图只是坐在那里没有交互,这将导致视图每隔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];
}
}
Run Code Online (Sandbox Code Playgroud)
我仍然是一个n00b,可能没有做到最好的一切.建议随时欢迎.
小智 23
我已经实现了Bobby的建议,但是在Swift中.代码概述如下.
创建一个新文件 - > 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?.contains(where: { $0.phase == .began || $0.phase == .moved }) == true {
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)
}
}
Run Code Online (Sandbox Code Playgroud)创建一个新文件 - > Swift文件 - > main.swift(名称很重要).
import UIKit
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
Run Code Online (Sandbox Code Playgroud)在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")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)请记住,您可能必须在applicationDidTimout中执行不同的操作,具体取决于您的根视图控制器.有关如何投射视图控制器的更多详细信息,请参阅此文章.如果你有导航控制器的模态视图,你可能想使用visibleViewController而不是topViewController.
kfm*_*e04 16
背景[快速解决方案]
有人要求用Swift更新这个答案,所以我在下面添加了一个片段.
请注意,我已经根据自己的用途修改了规格:如果没有UIEvents
5秒,我基本上想要工作.任何传入触摸UIEvent
都将取消之前的计时器并使用新计时器重新启动.
与上述答案的差异
init()
立即设置了计时器.此外,我reset_idle_timer()
将取消之前的计时器,因此任何时候只能运行一个计时器.重要提示:构建前的两个步骤
感谢SO上的几个很好的答案,我能够将上面的代码改编为Swift代码.
请按照这个答案了解如何UIApplication
在Swift中进行子类化.确保您遵循Swift的这些步骤,否则下面的代码段将无法编译.由于链接的答案描述了这些步骤,我在此不再重复.您需要不到一分钟的时间来正确阅读和设置它.
我无法得到NSTimer
的 cancelPreviousPerformRequestsWithTarget:
工作,所以我发现这个更新GCD解伟大的工程.只需拖放代码到一个单独的文件.swift和你GTG(这样你就可以打电话delay()
和cancel_delay()
和使用dispatch_cancelable_closure
).
恕我直言,下面的代码很简单,任何人都可以理解.我提前道歉没有回答有关这个答案的任何问题(有点充斥着工作atm).
我刚刚发布了这个答案,以回馈SO我已经得到了什么很好的信息.
片段
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()
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
47124 次 |
最近记录: |