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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8085188/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 15:20:10  来源:igfitidea点击:

iOS perform action after period of inactivity (no user interaction)

iosxcodetimer

提问by BobbyScon

How can I add a timer to my iOS app that is based on user interaction (or lack thereof)? In other words, if there is no user interaction for 2 minutes, I want to have the app do something, in this case navigate to the initial view controller. If at 1:55 someone touches the screen, the timer resets. I would think this would need to be a global timer so no matter which view you are on, the lack of interaction starts the timer. Although, I could create a unique timer on each view. Does anyone have any suggestions, links or sample code where this has been done before?

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

回答by BobbyScon

The link that Anne provided was a great starting point, but, being the n00b that I am, it was difficult to translate into my existing project. I found a blog [original blog no longer exists] that gave a better step-by-step, but it wasn't written for XCode 4.2 and using storyboards. Here is a write up of how I got the inactivity timer to work for my app:

Anne 提供的链接是一个很好的起点,但是,作为我的 n00b,很难将其转化为我现有的项目。我发现了一个博客 [原始博客不再存在] 提供了一个更好的一步一步,但它不是为 XCode 4.2 和使用故事板编写的。以下是我如何让不活动计时器为我的应用程序工作的文章:

  1. Create a new file -> Objective-C class -> type in a name (in my case TIMERUIApplication) and change the subclass to UIApplication. You may have to manually type this in the subclass field. You should now have the appropriate .h and .m files.

  2. Change the .h file to read as follows:

    #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. Change the .m file to read as follows:

    #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
    
  4. Go into your Supporting Files folder and alter main.m to this (different from prior versions of 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. Write the remaining code in your AppDelegate.m file. I've left out code not pertaining to this process. There is no change to make in the .h file.

    #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];
    }
    
  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 || 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
    
  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];
    }
    

Notes: The timer will start anytime a touch is detected. This means that if the user touches the main screen (in my case "mainView") even without navigating away from that view, the same view will push over itself after the allotted time. Not a big deal for my app, but for yours it might be. The timer will only reset once a touch is recognized. If you want to reset the timer as soon as you get back to the page you want to be at, include this code after the ...pushViewController:controller animated:YES];

注意:只要检测到触摸,计时器就会启动。这意味着如果用户触摸主屏幕(在我的例子中是“mainView”),即使没有导航离开该视图,在分配的时间后,相同的视图也会自动推过自己。对我的应用程序来说没什么大不了的,但对你来说可能是。计时器只会在识别到触摸后重置。如果您想在返回到您想要访问的页面后立即重置计时器,请在 ...pushViewController:controller 动画:YES] 之后包含此代码;

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

This will cause the view to push every x minutes if it's just sitting there with no interaction. The timer will still reset every time it recognizes a touch, so that will still work.

如果它只是坐在那里没有交互,这将导致视图每 x 分钟推送一次。每次识别到触摸时,计时器仍会重置,因此它仍然有效。

Please comment if you have suggested improvements, especially someway to disable the timer if the "mainView" is currently being displayed. I can't seem to figure out my if statement to get it to register the current view. But I'm happy with where I'm at. Below is my initial attempt at the if statement so you can see where I was going with it.

如果您有改进建议,请发表评论,尤其是在当前显示“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];
    }
}

I am still a n00b and may have not done everything the best way. Suggestions are always welcome.

我仍然是一个 n00b,可能没有以最好的方式完成所有事情。建议总是受欢迎的。

回答by Vanessa Forney

I have implemented what Bobby has suggested, but in Swift. The code is outlined below.

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

  1. Create a new file -> Swift File -> type in a name (in my case TimerUIApplication) and change the subclass to UIApplication. Change the TimerUIApplication.swift file to read as follows:

    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: { 
    import UIKit
    
    UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
    
    .phase == .began ||
    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")
                }
            }
        }
    }
    
    .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) } }
  2. Create a new file -> Swift File -> main.swift (the name is important).

    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: { 
    import UIKit
    
    UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
    
    .phase == .began ||
    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")
                }
            }
        }
    }
    
    .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) } }
  3. In your AppDelegate: Remove @UIApplicationMainabove the AppDelegate.

    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 文件更改为如下所示:

    -(void)idleTimerExceeded{
              AppDelegate *appdelegate = [[UIApplication sharedApplication] delegate];
    
              if(appdelegate.idle){
                [[NSNotificationCenter defaultCenter] postNotificationName: kApplicationDidTimeOutNotification object:nil]; 
              }
    }
    
  2. 创建一个新文件 -> Swift File -> main.swift(名称很重要)。

     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()
    
    
       }
    }
    
  3. 在您的 AppDelegate 中:删除@UIApplicationMainAppDelegate 上方的内容。

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

Keep in mind you may have to do different things in applicationDidTimout depending on your root view controller. See this postfor more details on how you should cast your view controller. If you have modal views over the navigation controller, you may want to use visibleViewControllerinstead of topViewController.

请记住,根据您的根视图控制器,您可能需要在 applicationDidTimout 中做不同的事情。有关如何投射视图控制器的更多详细信息,请参阅这篇文章。如果您在导航控制器上有模态视图,您可能需要使用visibleViewController而不是topViewController

回答by kfmfe04

Background [Swift Solution]

背景【Swift 解决方案】

There was a request to update this answer with Swift so I've added a snippet below.

有人要求用 Swift 更新这个答案,所以我在下面添加了一个片段。

Do note that I have modified the specs somewhat for my own uses: I essentially want to do work if there are no UIEventsfor 5 seconds. Any incoming touch UIEventwill cancel previous timers and restart with a new timer.

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

Differences from Answer Above

与上述答案的差异

  • Some changes from the accepted answerabove: instead of setting up the first timer upon the first event, I set up my timer in init()immediately. Also my reset_idle_timer()will cancel the previous timer so only one timer will be running at any time.
  • 上面接受的答案有一些变化:我没有在第一个事件时设置第一个计时器,而是init()立即设置我的计时器。我reset_idle_timer()也将取消前一个计时器,因此任何时候都只会运行一个计时器。

IMPORTANT: 2 Steps Before Building

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

Thanks to a couple great answers on SO, I was able to adapt the code above as Swift code.

多亏了关于 SO 的几个很好的答案,我能够将上面的代码改编为 Swift 代码。

  • Follow this answerfor a rundown on how to subclass UIApplicationin Swift. Make sure you follow those steps for Swift or the snippet below won't compile. Since the linked answer described the steps so well, I will not repeat here. It should take you less than a minute to read and set it up properly.

  • I could not get NSTimer's cancelPreviousPerformRequestsWithTarget:to work, so I found this updated GCD solutionwhich works great. Just drop that code into a separate .swift file and you are gtg (so you can call delay()and cancel_delay(), and use dispatch_cancelable_closure).

  • 按照此答案了解如何UIApplication在 Swift 中进行子类化。确保您按照 Swift 的这些步骤操作,否则下面的代码段将无法编译。由于链接的答案很好地描述了步骤,我不会在这里重复。阅读并正确设置它应该需要不到一分钟的时间。

  • 我无法让NSTimer's cancelPreviousPerformRequestsWithTarget:工作,所以我发现这个更新的 GCD 解决方案效果很好。只需将该代码放入一个单独的 .swift 文件中,您就是 gtg(因此您可以调用delay()andcancel_delay()和使用dispatch_cancelable_closure)。

IMHO, the code below is simple enough for anyone to understand. I apologise in advance for not answering any questions on this answer (a bit flooded with work atm).

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

I just posted this answer to contribute back to SO what great information I've gotten out.

我刚刚发布了这个答案,以回馈我已经获得的重要信息。

Snippet

片段

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


   }
}

回答by David

Notes: The timer will start anytime a touch is detected. This means that if the user touches the main screen (in my case "mainView") even without navigating away from that view, the same view will push over itself after the allotted time. Not a big deal for my app, but for yours it might be. The timer will only reset once a touch is recognized. If you want to reset the timer as soon as you get back to the page you want to be at, include this code after the ...pushViewController:controller animated:YES];

注意:只要检测到触摸,计时器就会启动。这意味着,如果用户触摸主屏幕(在我的例子中是“mainView”),即使没有离开那个视图,在分配的时间之后,同一个视图也会推过去。对我的应用程序来说没什么大不了的,但对你来说可能是。计时器只会在识别到触摸后重置。如果您想在返回到您想要访问的页面后立即重置计时器,请在 ...pushViewController:controller 动画:YES] 之后包含此代码;

One solution to this problem of the same view beginning displayed again is to have a BOOL in the appdelegate and set this to true when you want to check for the user being idle and setting this to false when you have moved to the idle view. Then in the TIMERUIApplication in the idleTimerExceeded method have an if statement as below. In the viewDidload view of all the views where you want to check for the user beginning idle you set the appdelegate.idle to true, if there are other views where you do not need to check for the user being idle you can set this to false.

再次开始显示同一视图的这个问题的一种解决方案是在 appdelegate 中有一个 BOOL 并在您想要检查用户空闲时将其设置为 true 并在您移动到空闲视图时将其设置为 false 。然后在 TIMERUIApplication 的 idleTimerExceeded 方法中有一个 if 语句,如下所示。在要检查用户开始空闲的所有视图的 viewDidload 视图中,将 appdelegate.idle 设置为 true,如果还有其他视图不需要检查用户是否处于空闲状态,则可以将其设置为 false .

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

回答by Enamul Haque

Swift 3 example here

Swift 3 示例在这里

  1. create a class like .

    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()
                    }
                }
            }
    
        }
    }
    
  2. create another class named main.swiftpaste bellow code

    ##代码##
  3. don't forget to Remove @UIApplicationMainfrom AppDelegate

  4. Swift 3 complete source code is given to GitHub. GitHub link:https://github.com/enamul95/UserInactivity

  1. 创建一个类,如 .

    ##代码##
  2. 创建另一个名为main.swift 的类粘贴波纹管代码

    ##代码##
  3. 不要忘记从 AppDelegate 中删除@UIApplicationMain

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

回答by Alan

Swift 3.0 Conversion of the subclassed UIApplicationin Vanessa's Answer

UIApplicationVanessa's Answer 中子类的 Swift 3.0 转换

##代码##