Auto Signout timer in app? - ios

What I need to do is this; I will have a timer that will tick away and when 30 minutes is up I'll auto signout the user. But if there's any interaction with the application I will reset the timer to 30 min. I have an idea on what to do but I'm sure there's a better way to accomplish this.
What I'll do is make a singleton class that holds a timer and posts a notification when the timer is up. So what I'm thinking is I'll have to reset the timer when ever the user presses a button, goes to the next screen etc.
My quesiton though is is it possible to respond to any touches in the app in one piece of code? Like somehow there's a superclass I can add this to and it will always reset the timer no matter what kind of interaction has happened? Or do I need to add the code to all the places where the user will interact with the application?

You can try this, subclass UIApplication and add following code in implementation
#implementation MyApplication
- (instancetype)init {
self = [super init];
if (self) {
[self reset];
}
return self;
}
- (void)reset {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(logout) object:nil];
[self performSelector:#selector(logout) withObject:nil afterDelay:30*60];
}
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
[self reset];
NSLog(#"event detected");
}
- (void)logout {
NSLog(#"logout now");
}
#end
Then in main.m change the implementation like this
return UIApplicationMain(argc, argv, NSStringFromClass([MyApplication class]), NSStringFromClass([AppDelegate class]));
Here what is happening is, - (void)sendEvent:(UIEvent *)event method will get called after each user activity, Then we are registering a perform selector request after 30 mins. Once user touches the screen within 30 mins cancel previous request and register new one.

Related

How to perform delayed selector safely iOS

In viewDidAppear I show a popup to users after 3 seconds. What if user navigates to another viewController after timer begins. The selected function will try to execute & show popup when superview is no longer on screen. App does not crash or throw any errors but I want to confirm this is safe. Should I set a BOOL and assert isCurrentView is YES, within selector method?
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self performSelector:#selector(showPopup) withObject:nil afterDelay:2.5];
}
in viewDidDisappear
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:(BOOL)animated];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showPopup) object:nil];
}

How can I create a UIWindow that will delay all events by 200 ms?

I'd like to create a custom UIWindow class that will delay all events by 200 ms.
Is it possible? If yes, how?
OK, I created a custom UIApplication class and everything worked fine:
- (void)sendEvent:(UIEvent *)event
{
[super sendEvent:event]; NSLog(#"Viola!");
}
But once I added this extra code to make it delay the events it didn't send any events.
- (void)sendEvent:(UIEvent *)event
{
[self performSelector:#selector(handleEventsAfterDelay:) withObject:event afterDelay:0.500];
}
- (void)handleEventsAfterDelay:(UIEvent *)event
{
[super sendEvent:event]; NSLog(#"Viola!");
}
Also, this doesn't work:
-(void)sendEvent:(UIEvent *)event
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.2]];
[super sendEvent:event];
}
Everything that delays the events doesn't fire at the end. I'm standing frustrated staring in a blank black screen as the app won't even start.
EDIT: I found a way around this problem. Yet, to this day, there's no officially plausible and safe way of doing this but I guess it's just not significant at all.
First, I don't think it's a good idea to delay device event. I would try to work around it some how. Also, I'm not sure if Apple will aprove delayed events.
Another reason for me not to try to delay events, is because I'm no guru. I have no idea how the app will start to behave.
That said, as SomeGuy suggested, you could subclass UIApplication and override all event handling methods relevant to your app. Take a look at the UIApplication class's reference.
I've never delayed native events, but I did work with some event manipulation bafore. I remember that back then, overriding sendEvent: was enough for what I was doing.
You could override sendEvent: as follows:
- (void)sendEvent:(UIEvent *)event
{
[NSThread sleepForTimeInterval:.2];
[super sendEvent:event];
}
This will delay all event 200 ms. This will however block your main thread.
I've tried to use a call to dispatch_after(3) but it didn't work.
If you only want to delay certain event types, use the UIEvent's type and subtype properties. Take a look at the UIEvent class's reference.
Again, I never had to delay, nor do I know what you're exactly trying to accomplish, but I would suggest to find an alternative to delaying the device's event.
Also, don't forget you have to tell Cocoa Touch to use your custom UIApplication. Go to the main.m file and add a principal class name to the UIApplicationMain call:
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, NSStringFromClass([YourApplication class]), NSStringFromClass([YourAppDelegate class]));
}
}
As a reference, this was my overridden method:
- (void)sendEvent:(UIEvent *)event
{
[super sendEvent:event];
// Was it a touch?
if (event.type == UIEventTypeTouches) {
// Get touch phase.
NSSet *allTouches = [event allTouches];
UITouchPhase phase = [((UITouch *)[allTouches anyObject]) phase];
// Check what to do.
switch (phase) {
case UITouchPhaseBegan:
// Reset counter.
[self.counter resetCount];
break;
case UITouchPhaseEnded:
case UITouchPhaseCancelled:
// Start counter.
[self.counter startCount];
break;
default:
break;
}
}
}
This was used to auto-sign out users from the app after some inactive time (it was a kiosk app).
Unfortunately, it's the best I can do. Maybe someone with more experience can give their input.
I hope this help.
I suppose the problem you have is caused by the UIEvent timestamp. I suppose that events will expire if it is not delivered within a short interval.
You could try adding setTimestamp: by a category and modify it accordingly but the change has to be very exact.

Remote Control for audio doesn't work

after I managed to play audio in background with MPMoviePlayerController and tried to make it receive remote controls. But when I click on the Play/Pause button there's no reaction and the audio keeps on playing.
Then I tried to show if there's a log-output but there's no output.
Here's my Code:
-(void)viewDidAppear:(BOOL)animated{
...
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
NSLog(#"remoteControlReceivedWithEvent: %#", event);
if (event.type==UIEventTypeRemoteControl) {
if (event.subtype==UIEventSubtypeRemoteControlPlay) {
NSLog(#"Play");
} else if (event.subtype==UIEventSubtypeRemoteControlPause) {
NSLog(#"Pause");
} else if (event.subtype==UIEventSubtypeRemoteControlTogglePlayPause) {
NSLog(#"Play Pause");
}
}
}
Thanks for your effort in advanced.
It looks like your view controller isn't in the responder chain.
That might be due to [self resignFirstResponder] in viewWillDisappear and/or another ViewController is in the front.
To make sure you receive these events you can implement a UIApplication subclass which will be able to receive the events always. So it doesn't matter which ViewController is visible.
The UIApplication is always in the end of the responder chain.
Create a subclass for UIApplication (MyUIApplication) and implements the following UIResponder methods:
- (void)remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
...
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
In main.m use the following to initiate the app (replace MyUIApplication and AppDelegate with your classes names):
#autoreleasepool {
return UIApplicationMain(argc, argv, NSStringFromClass([MyUIApplication class]), NSStringFromClass([AppDelegate class]));
}

Is there a way to detect a touch on the onscreen keyboard?

I don't care about which key was pressed, or how long, or anything like that. All I need is a way to detect that the user has touched the screen, even if that happens to be on the part of the screen covered by a keyboard.
My goal here is to detect a lack of interaction to 'reset' the app to a default state if left along long enough -- think a 'kiosk mode' application. The issue is that I can't detect when the keyboard is touched, as the keyboard apparently intercepts all touch events, even before my customer window can handle them.
Edit:
Considered (and dismissed) is the use of just keyboard show and hide notifications -- we need to prolong screen display if the user is actively typing. Since we use UIWebViews to display certain things, we also can't use the delegate methods of UITextViews or UITextFields.
It sounds like detecting all touches—on the keyboard and elsewhere—would suffice. We can do that by subclassing UIApplication to override sendEvent:.
We'll extend the UIApplicationDelegate protocol with a new message, application:willSendTouchEvent:, and we'll make our UIApplication subclass send the message to its delegate before handling any touch event.
MyApplication.h
#interface MyApplication : UIApplication
#end
#protocol MyApplicationDelegate <UIApplicationDelegate>
- (void)application:(MyApplication *)application willSendTouchEvent:(UIEvent *)event;
#end
MyApplication.m
#implementation MyApplication
- (void)sendEvent:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
id<MyApplicationDelegate> delegate = (id<MyApplicationDelegate>)self.delegate;
[delegate application:self willSendTouchEvent:event];
}
[super sendEvent:event];
}
#end
We'll need to make our app delegate conform to the MyApplicationDelegate protocol:
AppDelegate.h
#import "MyApplication.h"
#interface AppDelegate : UIResponder <MyApplicationDelegate>
// ...
AppDelegate.m
#implementation AppDelegate
- (void)application:(MyApplication *)application willSendTouchEvent:(UIEvent *)event {
NSLog(#"touch event: %#", event);
// Reset your idle timer here.
}
Finally, we need to make the app use our new MyApplication class:
main.m
#import "AppDelegate.h"
#import "MyApplication.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv,
NSStringFromClass([MyApplication class]),
NSStringFromClass([AppDelegate class]));
}
}
How about using the keyboardShow and keyBoardHide Notifications to set a timer? After x seconds you return the app to your required state.
Could even reset the timer in scrollview delegates or textField delegates if you want to.
Take a look at:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
And a method like this:
- (void)keyboardDidShow:(NSNotification *)note {
idleTimer = [NSTimer scheduledTimerWithTimeInterval:120
target:nil
selector:#selector(yourMethodHere)
userInfo:nil
repeats:NO];
}
Be sure to declare and #synthesize your timer in the .h and .m file.
Hope it helps.
UITextField or UITextView has a delegate method to detect when user types something:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// user tapped a key
//
// reset your "idle" time variable to say user has interacted with your app
}
Remember to conform to the UITextField or UITextView protocol depending which one you use (maybe both if you've got both text field and text views). Also remember to mark each of your text field or text view's delegate as your view controller.
<UITextFieldDelegate,UITextViewDelegate>
Updated Answer
Ron, not sure if you actually googled but I found this:
iPhone: Detecting user inactivity/idle time since last screen touch

How do I configure my app to detect shake motion events?

So let me clarify first off with showing what code i have implemented
-(BOOL)canBecomeFirstResponder
{
return YES;
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self becomeFirstResponder];
}
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated
{
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
NSLog(#"FUUU");
}
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
NSLog(#"FUUU");
}
}
this is all in a UIViewController class
and in - (void) applicationDidFinishLaunching:(UIApplication*)application i have set
[application setApplicationSupportsShakeToEdit:YES];
Yet it does nothing, not nary single motion detected. I'm not sure what else to do. This seems to have worked for many other people, so i am baffled as to why i am different...Could it be because it's through a UINavigationController? or because i load the main application via a plist and my main looks like so
retVal = UIApplicationMain(argc, argv, nil, nil);
I am quite thoroughly stumped.
In order to detect shake gesture you need to subclass the UIView and implement the following methods (Do not implement these methods on UIViewController):
(BOOL)canBecomeFirstResponder
(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
Then add the subclassed view to UIViewController and make it first responder by calling -becomeFirstResponder on it.
You don't need to set [application setApplicationSupportsShakeToEdit:YES]; because this is the default value and iOS use this only for Shake to Undo/Redo.
If you want to capture the motion in your view controller you need to set this property to NO [application setApplicationSupportsShakeToEdit:NO]; and handle it by yourself.
As described in Event Handling Programming Guide.
Hope this helps.
PS: Just in case, you call the wrong super in your viewDidAppear method. This should be :
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
Thank you all for your responses but i ended up using a different method i accessed the accelerometer in the app delegate and then send a nsnotifcation via the nsnotification center to the current ui navigation controller displayed
- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:nil userInfo:nil];
}

Resources