iOS在不活动一段时间后执行操作(无用户交互)

时间:2011-11-10 19:28:58

标签: ios xcode timer

如何根据用户互动(或缺少用户互动)向我的iOS应用添加计时器?换句话说,如果2分钟内没有用户交互,我想让应用程序执行某些操作,在这种情况下导航到初始视图控制器。如果在1:55有人触摸屏幕,则计时器重置。我认为这需要一个全局计时器,所以无论你在哪个视图,缺乏交互启动计时器。虽然,我可以在每个视图上创建一个独特的计时器。有没有人有任何建议,链接或示例代码以前做过这些?

6 个答案:

答案 0 :(得分:115)

Anne提供的链接是一个很好的起点,但是,作为我的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. 进入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]));
        }
    }
    
  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];
    }
    
  6. 注意:只要检测到触摸,计时器就会启动。这意味着如果用户触摸主屏幕(在我的情况下为“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,可能没有做到最好的一切。建议随时欢迎。

答案 1 :(得分:21)

我已经实现了Bobby的建议,但是在Swift中。代码概述如下。

  1. 创建新文件 - &gt; Swift文件 - &gt;键入一个名称(在我的情况下 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. 创建新文件 - &gt; Swift文件 - &gt; 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")
                }
            }
        }
    }
    
  4. 请记住,您可能必须在applicationDidTimout中执行不同的操作,具体取决于您的根视图控制器。有关如何投射视图控制器的更多详细信息,请参阅this post。如果您有导航控制器的模态视图,则可能需要使用visibleViewController而不是topViewController

答案 2 :(得分:14)

背景[Swift Solution]

有人请求使用Swift更新此答案,因此我在下面添加了一个代码段。

请注意我已根据自己的用途修改了规格:如果没有UIEvents 5秒钟,我基本上想要工作。任何传入的触摸UIEvent都将取消之前的定时器并使用新的计时器重新启动。

与上述答案的区别

  • 上面accepted answer的一些变化:我没有在第一个事件中设置第一个计时器,而是立即在init()设置我的计时器。此外,我的reset_idle_timer()将取消之前的计时器,因此任何时候只能运行一个计时器。

重要提示:构建前的两个步骤

感谢SO上的几个很好的答案,我能够将上面的代码改编为Swift代码。

  • 关注this answer,了解如何在Swift中继承UIApplication。确保您遵循Swift的这些步骤,否则下面的代码段将无法编译。由于链接的答案描述了这些步骤,我在此不再重复。您需要不到一分钟的时间才能正确阅读和设置。

  • 我无法让NSTimer的{​​{1}}工作,所以我发现这个updated GCD solution效果很好。只需将该代码放入单独的.swift文件即可,您就可以调用cancelPreviousPerformRequestsWithTarget:delay(),然后使用cancel_delay())。

恕我直言,下面的代码很简单,任何人都可以理解。我提前道歉没有回答有关这个答案的任何问题(有点充斥着工作atm)。

我刚刚发布了这个答案,以回馈SO我已经获得了哪些好消息。

<强>段

dispatch_cancelable_closure

答案 3 :(得分:4)

  

注意:只要检测到触摸,计时器就会启动。这意味着   如果用户触摸主屏幕(在我的情况下是“mainView”)甚至   如果没有离开那个视图,相同的视图将会推翻   在规定的时间之后。对我的应用来说并不是什么大不了的事,但对于   你的可能。只有触摸时,计时器才会重置   认可。如果您想在返回后立即重置计时器   您要访问的页面,包含此代码   ... pushViewController:控制器动画:是];

同一视图开始显示的这个问题的一个解决方案是在appdelegate中有一个BOOL,当你想要检查用户是否空闲时将其设置为true,并在你移动到空闲时将其设置为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]; 
          }
}

答案 4 :(得分:4)

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. 不要忘记从AppDelegate删除 @UIApplicationMain

  4. Swift 3完整源代码提供给GitHub。 GitHub链接:https://github.com/enamul95/UserInactivity

答案 5 :(得分:2)

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

    }
}