Detecting user inactivity in iOS application!
One of my main task nowadays is to develop new applications from scratch.
This one provided numerous health services and of course, it has to deal with data.
However, health is one of these fields where privacy is one of the top priority. Guidelines must be follow regarding security, to keep data safe and private.
Hence, in order to prevent data leak and offer the best security possible, one of the security measure, we must provide is to lock the system after a some time of inactivity. By lock, we mean shut down the access, and in the context of the app, disconnect the user. The user must then log in to access the app and its data.
That’s where the problem is: how to detect the inactivity of the user in our app and perform actions? In our case, disconnecting the user.
To do that, first we need to go back to basis and understand how iOS app are launched and how the events are handled.
Understanding iOS lifecycle
Every iOS app has exactly one instance of
UIApplication, which is responsible for handling and routing user event to the good objects. That’s where we need to dive into, because we want to be at the lowest level possible.
First things first, when this instance is created?
Well the creation is the responsibility of one method,
This function instantiates the application object from the principal class and instantiates the delegate (if any) from the given class and sets the delegate for the application. It also sets up the main event loop, including the application’s run loop, and begins processing events.
— Apple Documentation
But same problem apply here, when did that method is called?
As a matter of fact, did you ever notice there is no entry point, or main method in an iOS app? Well, that is not entirely true. In Objective-C, there is one, and it calls the
UIApplicationMain(_:_:_:_:) method I mention just before. Okay that solves one problem. But where did it go in Swift based app?
I’m sure you already took a closer look at the
AppDelegate.swift file and notice the
Apply this attribute to a class to indicate that it is the application delegate. Using this attribute is equivalent to calling the
UIApplicationMainfunction and passing this class’s name as the name of the delegate class.
— Swift, Language Reference, Attributes
So basically, in Swift app, the main file is not needed when using this attribute ; all we need to do is provide a delegate, by default the
AppDelegate class, and the system will handle the call for us. Really handy.
When the app is launched, the
UIApplication is created and then when everything is ready, the delegate
UIApplicationDelegate is called, thanks to its delegate method
application(didFinishLaunchingWithOptions:) allowing us to perform custom setup (or not if you’re using Storyboard).
Okay, okay, back to our problem. We want to monitor all of the user events, do our little custom work, and propagate the events to its rightful control objects. And we want to do in our whole app, quickly, easily, without adding protocols and code everywhere. That would become messy really quick.
Our custom UIApplication
We said that the
UIApplication singleton is responsible for handling user events. Well to do our custom actions, let’s simply subclass it.
We define a timer, and whenever a touch on the screen occurs, we reset this timer. If the timer reach its limit, we post a notification.
The last method,
sendEvent(_) is the most interesting. Calling the parent method at first will dispatch the event that we intercept back to the system. Then we check the event is a touch screen event, and if so reset the timer.
Okay. So now, we have a working system that can monitor user events without interfering and notify us when some conditions are reunited, in our case, a timing condition.
But how to use our custom subclass as the default
UIApplication instance at app launch?
Back to Objective-C?!
As I said, the attribute
@UIApplicationMain is a shortcut that will call the
UIApplicationMain() method, which is responsible for creating the
UIApplication singleton. Did you remember how Objective-C does it? All we have to do is call
UIApplicationMain(_:_:_:_:) by ourself and passing it our
main.swift file (the name of the file is important), with:
Let’s analyze it, part by part. The method takes four parameters:
argc: the count of arguments in
argv; this is provided by the
principalClassName: the name of the
UIApplicationclass or subclass. If you specify nil, UIApplication is assumed ; we want to use our subclass, we provide it
delegateClassName: the name of the class from which the application delegate is instantiated, must conform to
AppDelegatestill do the job
@UIApplicationMain from AppDelegate and you’re done! Build & run..
..and still nothing happens! We forgot to add an observer to our notification!
I choose to do all the handling in AppDelegate, because I wanted to monitor the events from the start.
In AppDelegate, add the following method.
And then register the observer in
application:didFinishLaunchingWithOptions, like this:
applicationDidTimeout will be call after a certain time of inactivity and you can perform your custom actions.
As any solution, it has its strengths and weaknesses.
we did not modify a lot of code of our app
the process is low-level
our subclass of
UIApplicationis independent, we can add or modify the conditions of inactivity without impacting the rest of the app
notification allows us to handle the event anywhere in the app ; we could extend the
UIApplicationDelegateand provide and a new delegate method, but then we could only handle in the AppDelegate ; in my example I handle in the AppDelegate, because of the context, but you could start observing the notification later in your app flow
process is kind of heavy for small app, with one or two views (like game) ; instead apply it directly on the views
the timeout is setup right after the first touch ; what if you only want to enable it after some point in your app (like login screen, menu)? We could add helpers functions such as enable/disable timer for example
the timer continues after the app goes to background or the device is locked ; we could enable/disable the timer or register/unregister the notification in
Our problem is now resolved! We are notified of user inactivity and we can take measures, depending on the context, such as:
show a message, maybe the user is stucked on something (help, UX)
disconnect the user (security)
launch an idlling mode (power)
Last updated on 02nd September 2019