1

I have a basic application for iPads which consists of 4 views. Across the whole app, I want to be able to detect user inactivity and then display an Alert after 4 minutes which will ask the user if they are still there.

I have found some useful resources for the Timer and Alert functions. I have played around with these tutorials and can get the Timer working on it's own. However, this is my first time developing in Swift so I would like some guidance on the best way to connect the Timer to the Alert is? I would like the Timer to run for 4 minutes and then create the Alert.

I would also like to know where the best place is to put the code for these elements so that they work across all 4 of my views. E.g is there a single place I can put the code and reuse it rather than having it repeated in each of the 4 views?

pmh
  • 51
  • 1
  • 5

3 Answers3

1

First you can follow the instructions here to set up your timer: https://blog.gaelfoppolo.com/detecting-user-inactivity-in-ios-application-684b0eeeef5b

Then you can do something like this to handle the timeout notifications:

extension UIViewController {
    func observeTimeout() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleTimeout),
            name: .appTimeout,
            object: nil)
    }

    @objc func handleTimeout() {
        let alert = UIAlertController(title: "Timeout", message: "Oh no!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
        }))
        present(alert, animated: true, completion: nil)
    }
}

And then in each of your view controllers do this to register for the timeout:

class SomeViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        observeTimeout()
    }
}
Rob C
  • 4,877
  • 1
  • 11
  • 24
1

It's been a while since this question was asked however, since I came across the same problem I slightly changed the answer @rob-c provided using this guide:

Tested on Swift 5.5 and iOS 15.1


1- Create a subclass of UIApplication

import Foundation
import UIKit

class TimerApplication: UIApplication {

    private var timeoutInSeconds: TimeInterval {
        return 40.0
    }

    private var idleTimer: Timer?
    
    override init() {
        super.init()
        resetIdleTimer()
    }
    
    private func resetIdleTimer() {
        if let idleTimer = idleTimer {
            idleTimer.invalidate()
        }

        idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds,
                                         target: self,
                                         selector: #selector(TimerApplication.timeHasExceeded),
                                         userInfo: nil,
                                         repeats: false
        )
    }

    @objc private func timeHasExceeded() {
        NotificationCenter.default.post(name: .appTimeout, object: nil)
    }

    override func sendEvent(_ event: UIEvent) {

        super.sendEvent(event)

        if idleTimer != nil {
            self.resetIdleTimer()
        }

        if let touches = event.allTouches {
            for touch in touches where touch.phase == UITouch.Phase.began {
                self.resetIdleTimer()
            }
        }
    }
}

2- Add notification name

import Foundation

extension Notification.Name {

    static let appTimeout = Notification.Name("appTimeout")

}

3- Create main.swift file and add this

import Foundation
import UIKit

/// NOTE: comment out @UIApplicationMain in AppDelegate
UIApplicationMain(
    CommandLine.argc,
    CommandLine.unsafeArgv,
    NSStringFromClass(TimerApplication.self),
    NSStringFromClass(AppDelegate.self)
)

4- Remove @UIApplicationMain from AppDelegate

5- Add observer for the notification

Add the observer where appropriate for your case, in AppDelegate or any view controller:

func addObservers() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(idleTimeLimitReached(_:)),
                                               name: .appTimeout,
                                               object: nil)
    }
 
@objc func idleTimeLimitReached(_ notification: Notification) {
        print("***** IDLE Time called")
        
    }
Dharman
  • 30,962
  • 25
  • 85
  • 135
Asteroid
  • 1,049
  • 2
  • 8
  • 16
-1

You can run Timer in AppDelegate itself and display alert like below from AppDelegate

func showAlertFromAppDelegates(){
    let alertVC = UIAlertController(title: "Oops" , message: "Presented Alert from AppDelegates", preferredStyle: UIAlertController.Style.alert)
    let okAction = UIAlertAction(title: "Okay", style: UIAlertAction.Style.cancel) { (alert) in

    }
    alertVC.addAction(okAction)

   var presentVC = self.window!.rootViewController
   while let next = presentVC?.presentedViewController
   {
       presentVC = next
   }
   presentVC?.present(alertVC, animated: true, completion: nil)

}

Edit: Timer related code added, call startTimer() when you want to start inactivity and call stopTimer() when inactivity ends

func startTimer()
    {
        let interval = 4*60
        timer = Timer.scheduledTimer(timeInterval: TimeInterval(interval), target: self, selector: #selector(self.showAlertFromAppDelegates), userInfo: nil, repeats: true)
    }
    func stopTimer()
    {
        if let timer = timer
        {
            timer.invalidate()
        }
        timer = nil
    }
Asad Amodi
  • 136
  • 4
  • 13
  • 2
    How does this detect inactivity? – Joakim Danielson Dec 11 '19 at 11:25
  • My answer is for " I would like some guidance on the best way to connect the Timer to the Alert". – Asad Amodi Dec 11 '19 at 11:28
  • My assumption is he knows how to detect inactivity he just need help to manage Timer and Alert. – Asad Amodi Dec 11 '19 at 11:29
  • Well I disagree on that but let's see if OP comes back with some clarification. – Joakim Danielson Dec 11 '19 at 11:30
  • Well he is clearly saying that "I would also like to know where the best place is to put the code for these elements so that they work across all 4 of my views" – Asad Amodi Dec 11 '19 at 11:31
  • Sorry for not being clear this is actually the first question I have ever asked on here I can get the Timer working already, using the exact code in the link I added in the question. I just want to know how to create the Alert based on the Timer reaching 4 minutes. – pmh Dec 11 '19 at 11:38
  • I'm having trouble with "var presentVC = self.window!.rootViewController" I was getting this error "Value of type 'AppDelegate' has no member 'window'" So I added this line at the top of my AppDelegate class to fix the issue "var window: UIWindow? { get set }" But now I am getting another error I can't fix "Expected '{' to start getter definition" – pmh Dec 12 '19 at 09:50
  • remove {get set} and try – Asad Amodi Dec 12 '19 at 10:29
  • @AsadAmodi thank you that has resolved that error. I am going to try implementing the timer to start/stop based on UITapGestureRecognizer and will see if this all works – pmh Dec 12 '19 at 10:59