0

I have an application that responds to user input, but there are segments where there is animation effects that I don't want the user to be able to interrupt in any way. To accomplish this, I have a class that is linked/instantiated with any object/scene that should respond to user input. Each method in that class (began, moved, ended) checks the following class-level "allowInput" method before doing any further processing:

//  application-level flag indicating if user touch/click input is currently enabled...
static BOOL g_fAllowUserInput_ = YES;


//  these class-level methods can be called from multiple threads, so create a single-thread-only
//  lock for accessing the current mode...
#import <pthread.h>
static pthread_mutex_t _mutexSerializeCurrentModeAllowInput = PTHREAD_MUTEX_INITIALIZER;


//  global class methods to handle application-wide user touch/click input permissions...
@implementation TouchHandler ( Allow )


//  report if user touch/click input is allowed...
+ ( BOOL ) allowUserInput

{

    //  access the single-thread-only lock...
    pthread_mutex_lock( &_mutexSerializeCurrentModeAllowInput );

    //  get the current mode...
    BOOL const mode = g_fAllowUserInput_;

    //  release the single-thread-only lock...
    pthread_mutex_unlock( &_mutexSerializeCurrentModeAllowInput );

    //  return the current "touch enabled" status...
    return mode;

}  //  end TouchHandler ( Allow )::allowUserInput


//  enable/disable user touch/click input...
+ ( void ) setAllowUserInput: ( BOOL const ) allow

{

    //  access the single-thread-only lock...
    pthread_mutex_lock( &_mutexSerializeCurrentModeAllowInput );

    //  regardless of what the current status is, we need to unschedule the automatic "force allow input"
    //  method, if it's running, since the application is still functioning properly...
    [ [ [ CCDirector sharedDirector ] scheduler ] unscheduleSelector: @selector( forceAllowInput: )
                                                           forTarget: self
    ];

    //  set the new "input enabled" status...
    g_fAllowUserInput_ = allow;

    //  if user input is supposed to be suspended...
    if ( ! allow )

        {

        //  schedule the automatic "force allow input" method, to make sure that we don't block out the
        //  user if something gets "screwy" in the application...
        [ [ [ CCDirector sharedDirector ] scheduler ] scheduleSelector: @selector( forceAllowInput: )
                                                             forTarget: self
                                                              interval: 5.0f
                                                                paused: NO
        ];

        }  // end disable user input

    //  release the single-thread-only lock...
    pthread_mutex_unlock( &_mutexSerializeCurrentModeAllowInput );

    return;

}  //  end TouchHandler ( Allow )::setAllowUserInput


//  scheduled method to force-enable user input if it's been disabled for an unexpected duration...
+ ( void ) forceAllowInput: ( ccTime ) delta

{

    //  access the single-thread-only lock...
    pthread_mutex_lock( &_mutexSerializeCurrentModeAllowInput );

    //  remove this method from the scheduler, since it's only necessary to fire it once...
    [ [ [ CCDirector sharedDirector ] scheduler ] unscheduleSelector: @selector( forceAllowInput: )
                                                           forTarget: self
    ];

    //  set the new "input enabled" status to "enabled"...
    g_fAllowUserInput_ = YES;

    //  release the single-thread-only lock...
    pthread_mutex_unlock( &_mutexSerializeCurrentModeAllowInput );

    return;

}  //  end TouchHandler ( Allow )::forceAllowInput


@end  //  end @implementation TouchHandler ( Allow )

"setAllowUserInput" is called at various places in the application, depending on whether or not I want user interaction to be suspended.

My question is: Is there a more efficient way of handling this on an almost-application-wide scale? I say "almost" because there is at least one control, a button, that captures the current screen and sends it as an email attachment, and that control needs to always be active. The scheduled delay of 5 seconds is an arbitrary value that I chose, but I hate doing things like that, so I'm looking for a way to have the application "sense" when something is wrong and user interaction has been disabled for too long.

Thanks for any help you can give....

miwalsh
  • 111
  • 2
  • 9

1 Answers1

3

UIApplication has two methods to do exactly this: beginIgnoringInteractionEvents and endIgnoringInteractionEvents.

The documentation is here

If you want to disable certain areas only this isn't the right approach.

For that, the simplest method is to add a view covering the parts you want to block and enable / disable interaction on that view. A plain, clear UIView with interaction enabled will swallow all touches going through to the view beneath. Turn off interaction, and the touches will pass through.

If the button doesn't fit into a nice rectangle you could look at overriding some of the touch handling methods (hitTest:withEvent:, probably) to only allow touches through at certain points.

jrturton
  • 118,105
  • 32
  • 252
  • 268
  • I've looked through the documentation that you referenced, but I didn't see anything describing what I'm achieving with the scheduled "forceAllowInput:" method. I'm trying to do the "disable user input" programmatically so that I can always post a dialog, or at least a button, if user interaction is disabled for too long, and allow the user to **only** interact with that... I also have a "print screen and send as email"-type button that is **always** enabled.... – miwalsh Jul 12 '14 at 13:44
  • You could achieve something like that with timers and checking the `isIgnoringInteractionEvents`. My answer was focussing on the "more efficient" aspect of your question - that code looks horrible, no offence, and checking values all the time on touch handling can't be the best way to handle whatever it is you're trying to do. – jrturton Jul 12 '14 at 13:49
  • Please, no offense taken, that's why I'm asking the question! :) Does this method allow me to keep the "print screen" button functional at all times? – miwalsh Jul 12 '14 at 13:59
  • No, but you asked for "application wide" disabling in the question. If you want to disable certain areas only this isn't the right approach, but you should probably edit the question. For that the simplest method is to add a view covering the parts you want to block and enable / disable interaction on that view. – jrturton Jul 12 '14 at 14:02
  • Thanks, I've updated the question to include the "almost-application-wide", and I will look into using a view. Maybe block the entire display, then add a new view over the "print screen" button area that is always enabled? – miwalsh Jul 12 '14 at 14:08
  • Thanks! I'll give it a shot and let you know! – miwalsh Jul 12 '14 at 14:19