23

Is it possible to somehow listen to, and catch, all the touch events occurring in an app?

The app I'm currently developing will be used in showrooms and information kiosks and I would therefore like to revert to the start section of the app if no touches has been received for a given couple of minutes. A sort of screensaver functionality, if you will. I'm planning to implement this by having a timer running in the background, which should be reset and restarted every time a touch event occurs somewhere in the app. But how can I listen to the touch events? Any ideas or suggestions?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
rodskagg
  • 3,827
  • 4
  • 27
  • 46

6 Answers6

37

You need a subclass of UIApplication (let's call it MyApplication).

You modify your main.m to use it:


return UIApplicationMain(argc, argv, @"MyApplication", @"MyApplicationDelegate");

And you override the method [MyApplication sendEvent:]:


- (void)sendEvent:(UIEvent*)event {
    //handle the event (you will probably just reset a timer)

    [super sendEvent:event];
}

Sulthan
  • 128,090
  • 22
  • 218
  • 270
7

A subclass of UIWindow could be used to do this, by overriding hitTest:. Then in the XIB of your main window, there is an object usually simply called Window. Click that, then on the right in the Utilities pane go to the Identities (Alt-Command-3). In the Class text field, enter the name of your UIWindow subclass.

MyWindow.h

@interface MyWindow : UIWindow
@end

MyWindow.m

@implementation MyWindow

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *res;

    res = [super hitTest:point withEvent:event];

    // Setup/reset your timer or whatever your want to do.
    // This method will be called for every touch down,
    // but not for subsequent events like swiping/dragging.
    // Still, might be good enough if you want to measure
    // in minutes.

    return res;
}   

@end
DarkDust
  • 90,870
  • 19
  • 190
  • 224
3

You can use a tap gesture recognizer for this. Subclass UITapGestureRecognizer and import <UIKit/UIGestureRecognizerSubclass.h>. This defines touchesBegan:, touchesMoved:, touchesEnded: and touchesCancelled:. Put your touch-handling code in the appropriate methods.

Instantiate the gesture recognizer in application:didFinishLaunchingWithOptions: and add it to UIWindow. Set cancelsTouchesInView to NO and it'll pass all touches through transparently.

Credit: this post.

bcattle
  • 12,115
  • 6
  • 62
  • 82
3

Create a Class "VApplication" that extends from UIApplication and paste these code to corresponding class

VApplication.h

#import <Foundation/Foundation.h>

// # of minutes before application times out
#define kApplicationTimeoutInMinutes 10

// Notification that gets sent when the timeout occurs
#define kApplicationDidTimeoutNotification @"ApplicationDidTimeout"

/**
 * This is a subclass of UIApplication with the sendEvent: method 
 * overridden in order to catch all touch events.
 */

@interface VApplication : UIApplication
{
    NSTimer *_idleTimer;
}

/**
 * Resets the idle timer to its initial state. This method gets called
 * every time there is a touch on the screen.  It should also be called
 * when the user correctly enters their pin to access the application.
 */
- (void)resetIdleTimer;

@end

VApplication.m

#import "VApplication.h"
#import "AppDelegate.h"

@implementation VApplication

- (void)sendEvent:(UIEvent *)event
{
    [super sendEvent:event];
    // Fire up the timer upon first event
    if(!_idleTimer) {
        [self resetIdleTimer];
    }

    // Check to see if there was a touch event
    NSSet *allTouches       = [event allTouches];

    if  ([allTouches count] > 0)
        {
        UITouchPhase phase  = ((UITouch *)[allTouches anyObject]).phase;
        if  (phase == UITouchPhaseBegan)
            {
            [self resetIdleTimer];         
            }
        }
}

- (void)resetIdleTimer 
{
    if  (_idleTimer)
        {
        [_idleTimer invalidate];
        }

    // Schedule a timer to fire in kApplicationTimeoutInMinutes * 60


//  int timeout =   [AppDelegate getInstance].m_iInactivityTime;
    int timeout =   3;
    _idleTimer = [NSTimer scheduledTimerWithTimeInterval:timeout
                                                  target:self 
                                                selector:@selector(idleTimerExceeded) 
                                                userInfo:nil 
                                                 repeats:NO];

}

- (void)idleTimerExceeded
{
    /* Post a notification so anyone who subscribes to it can be notified when
     * the application times out */


    [[NSNotificationCenter defaultCenter]
     postNotificationName:kApplicationDidTimeoutNotification object:nil];
}


@end

Replace the Class name "VApplication" to our

Main.m

file like this

int main(int argc, char * argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, @"VApplication", NSStringFromClass([AppDelegate class]));

    }
}

Register a notification for your corresponding view controller

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil];

And once the timeout occur the notification will fire and handle the event like this

- (void) applicationDidTimeout:(NSNotification *) notif //inactivity lead to any progress
{


}
Vicky
  • 1,095
  • 16
  • 15
  • Thanks, I needed to use this since UITapGestureRecognizer was not catching all touch events for me. – Pellet Feb 16 '18 at 06:02
1

You could put a transparent view at the top of the view hierarchy, and choose in that view whether to handle the touch events it receives or pass them through to lower views.

  • That'll only work for an app that shows only one view. It breaks for example as soon as you present another view controller. – DarkDust Dec 09 '11 at 10:44
  • 1
    Well, a `presentModalViewController:animated:` would obscure your view or am I missing something? Or are you suggesting to have the transparent view inserted into every view controller? That certainly would be a lot of work and fragile as you must always make sure to not accidentally put a view above it. Or is there an easy way to ensure this? – DarkDust Dec 09 '11 at 11:31
0

In Swift 4.2 1. Create subclass of UIApplication object and print user action:

    import UIKit
    class ANUIApplication: UIApplication {

        override func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool {
            print("FILE= \(NSStringFromSelector(action)) METHOD=\(String(describing: target!)) SENDER=\(String(describing: sender))")
            return super.sendAction(action, to: target, from: sender, for: event)
        }   
    }
  1. In AppDelegate.swift file you will find application entry point @UIApplicationMain Comment that and Add new swift file main.swift and add following code to main.swift file

import UIKit

UIApplicationMain( CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(ANUIApplication.self), NSStringFromClass(AppDelegate.self))

ANUIApplication is class where we added action logs. AppDelegate is default app delegate where we wrote application delegate methods.(Helpful for tracking action and file name in big project)

Avijit Nagare
  • 8,482
  • 7
  • 39
  • 68