Search code examples
iostimeouttouchappdelegateswift2

How to detect all touches in Swift 2


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?


Solution

  • 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.

    enter image description here