I'm trying to create a timeout function for an app I'm develop using Swift 2 but in swift 2, you can put this code in the app delegate and it works but it does not detect any keyboard presses, button presses, textfield presses, and etc:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event);
let allTouches = event!.allTouches();
if(allTouches?.count > 0) {
let phase = (allTouches!.first as UITouch!).phase;
if(phase == UITouchPhase.Began || phase == UITouchPhase.Ended) {
//Stuff
timeoutModel.actionPerformed();
}
}
}
Before swift 2, I was able to have the AppDelegate subclass UIApplication and override sendEvent: like this:
-(void)sendEvent:(UIEvent *)event
{
[super sendEvent:event];
// Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
// allTouches count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded)
[[InactivityModel instance] actionPerformed];
}
}
The code above works for every touch but the swift equivalent only works when a view does not exist above that UIWindow's hierarchy?
Does anyone know a way to detect every touch in the application?
As I have something similar in my application, I just tried to fix it:
- override
sendEvent
in UIWindow
- doesn't work
- override
sendEvent
in delegate - doesn't work
So the only way is to provide custom UIApplication
subclass. My code so far (works on iOS 9) is:
@objc(MyApplication) class MyApplication: UIApplication {
override func sendEvent(event: UIEvent) {
//
// Ignore .Motion and .RemoteControl event
// simply everything else then .Touches
//
if event.type != .Touches {
super.sendEvent(event)
return
}
//
// .Touches only
//
var restartTimer = true
if let touches = event.allTouches() {
//
// At least one touch in progress?
// Do not restart auto lock timer, just invalidate it
//
for touch in touches.enumerate() {
if touch.element.phase != .Cancelled && touch.element.phase != .Ended {
restartTimer = false
break
}
}
}
if restartTimer {
// Touches ended || cancelled, restart auto lock timer
print("Restart auto lock timer")
} else {
// Touch in progress - !ended, !cancelled, just invalidate it
print("Invalidate auto lock timer")
}
super.sendEvent(event)
}
}
Why there's @objc(MyApplication)
. That's because Swift mangles names in a different way then Objective-C and it just says - my class name in Objective-C is MyApplication
.
To make it working, open your info.plist and add row with Principal class key and MyApplication
value (MyApplication
is what's inside @objc(...)
, not your Swift class name). Raw key is NSPrincipalClass
.
UIWindow
also has a sendEvent
method that you can override. That would allow you to track the time since the last screen touch. Swift 4:
class IdlingWindow: UIWindow {
/// Tracks the last time this window was interacted with
var lastInteraction = Date.distantPast
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
lastInteraction = Date()
}
}
If you're using a storyboard, you can load it in didFinishLaunchingWithOptions
:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: IdlingWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = IdlingWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UIStoryboard.init(name: "Main", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
return true
}
:
}
extension UIApplication {
/// Conveniently gets the last interaction time
var lastInteraction: Date {
return (keyWindow as? IdlingWindow)?.lastInteraction ?? .distantPast
}
}
Now elsewhere in your app, you can check for inactivity like this:
if UIApplication.shared.lastInteraction.timeIntervalSinceNow < -2 {
// the window has been idle over 2 seconds
}