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
Related
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.
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.
Sorry for the basic Question, I'm fairly new to programming and trying to understand something in the code apple suggester for a certain solution to something I wanted to preform.
I created a simple notes app, very basic, currently I have:
1. CreateNote view controller
2. NotesList table view controller
So I wanted to add a behaviour when a note is being created and a user types below the keyboard so the text resized so the user can still see what he types and the text is not going behind the keyboard.
So I add some lines of code suggested by apple to accomplish that.
In the viewWillAppear called a method on NSNotificationCenter and I could not understand where is an NSNotificationCenter object is declared...?
So this is my CreateNote view controller(Please help me understand why they could preform this call):
#import "NMCreateNotesViewController.h"
#interface NMCreateNotesViewController () <UITextViewDelegate>
#property (weak, nonatomic) IBOutlet UIBarButtonItem *createButton;
#property (weak, nonatomic) IBOutlet UITextView *textField;
#end
#implementation NMCreateNotesViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// listen for keyboard hide/show notifications so we can properly adjust the table's height
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
#pragma mark - Notifications
- (void)adjustViewForKeyboardReveal:(BOOL)showKeyboard notificationInfo:(NSDictionary *)notificationInfo
{
// the keyboard is showing so resize the table's height
CGRect keyboardRect = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
NSTimeInterval animationDuration =
[[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect frame = self.textField.frame;
// the keyboard rect's width and height are reversed in landscape
NSInteger adjustDelta = UIInterfaceOrientationIsPortrait(self.interfaceOrientation) ? CGRectGetHeight(keyboardRect) : CGRectGetWidth(keyboardRect);
if (showKeyboard)
frame.size.height -= adjustDelta;
else
frame.size.height += adjustDelta;
[UIView beginAnimations:#"ResizeForKeyboard" context:nil];
[UIView setAnimationDuration:animationDuration];
self.textField.frame = frame;
[UIView commitAnimations];
}
- (void)keyboardWillShow:(NSNotification *)aNotification
{
[self adjustViewForKeyboardReveal:YES notificationInfo:[aNotification userInfo]];
}
- (void)keyboardWillHide:(NSNotification *)aNotification
{
[self adjustViewForKeyboardReveal:NO notificationInfo:[aNotification userInfo]];
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if (sender != self.createButton) return;
if (self.textField.text.length > 0) {
self.note = [[NMNote alloc] init];
self.note.content = self.textField.text;
}
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Is NSNotificationCenter is part of UITextView?
No it is not. NSNotificationCenter is - as it's name says - a notification center. Objects can subscribe to notifications and post notifications with NSNotificationCenter to handle and notify of certain events.
They are using NSNotificationCenter to have the viewcontroller subscribe to UIKeyboardWillHideNotification and UIKeyboardWillShowNotification events.
Take a look at this one:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
NSNotificationCenter is designed to be used as a singleton ( I believe this is the correct term, correct me if I'm wrong ) so we access the NSNotificationCenter for this app's process by calling the class method defaultCenter. it adds observer 'self' ( which in this case is an instance of the view controller) and basically instructs it to send the message keyboardWillShow to the observer when a Notification under the name of UIKeyboardWillShowNotification is fired.
What object fires the UIKeyboardWillShowNotification? Well it's not a UITextView, this notification name is actually defined in UIWindow.h so it probably came from there, which in turn probably was invoked from UIKeyboard which is not a public API as far as I know.
NSNotificationCenter is a class. In Objective-C, classes are declared in header files; this one is in NSNotification.h. (Try pressing Command-Shift-O and typing "NSNotificationCenter" to find this yourself.) When you want to use a class, you #import the header file that the class is declared in; this makes the compiler read that header file first and make all the classes (and other globals) available to your code to use.
This would be a huge pain, though, since a typical Cocoa app uses zillions of classes and other globals from Apple's libraries. So, instead, you just need to #import <Foundation/Foundation.h> which is a header file that just includes a bunch of other header files, including NSNotification.h. (The import statement for Foundation is probably in your own header file, or something else like #import <UIKit/UIKit.h> which also will include foundation and ultimately NSNotification.h.)
One final detail is that there's is probably a "prefix" header in your project which includes UIKit.h in all of your files automatically, so anything declared in there is always available to your code.
NSNotificationCenter is a class in Foundation.
NSNotificationCenter doesn't need to be declared and stored in a variable, its just a call, to explain it further, think of NSNotificationCenter as a tackboard where things get posted, you add a note to that backboard by creating the NSNotificationCenter, and you assingn observers to look at that board, and do something when a note is added.
The NSNotificationCenter object being used in this case is a Singleton. What you need to know is that sending the message defaultCenter to the NSNotificationCenter class object always returns the same NSNotificationCenter object.
Here's what the call to default center might look like
+ (NSNotificationCenter*)defaultCenter
{
static dispatch_once_t once;
static NSNotificationCenter* sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
/*
set up properties, etc
*/
});
return sharedInstance;
}
I am making an app that should support iOS versions from iOS5 onwards. It uses a UIAlertView, and if it's visible when the user presses the home button, I would like it to be dismissed before the user returns to the app (i.e. it's gone when the app is re-opened using multitasking). All methods in the app delegate show it as not visible (isVisible=NO) even if it is still visible when reopened. Is there a way to do this?
Thanks.
Or you inherit your class from UIAlertView and add NSNotification observer for UIApplicationWillResignActiveNotification and when notification occurs call to alertview method dismissWithClickedButtonIndex:
Example:
.h file
#import <UIKit/UIKit.h>
#interface ADAlertView : UIAlertView
#end
.m file
#import "ADAlertView.h"
#implementation ADAlertView
- (void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (id) initWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ... {
self = [super initWithTitle:title
message:message
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles, nil];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(dismiss:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
return self;
}
- (void) dismiss:(NSNotification *)notication {
[self dismissWithClickedButtonIndex:[self cancelButtonIndex] animated:YES];
}
#end
With your own class inherited from UIAlertView you are need not to store link to alertview or something else, only one thing that you must do its replace UIAlertView to ADAlertView (or any other class name).
Feel free to use this code example (if you are not using ARC, you should add to the dealloc method [super dealloc] after [[NSNotificatioCenter defaultCenter] removeObserver:self])
Keep a reference to the displayed UIAlertView in your app delegate. When you show the alert, set the reference; when the alert is dismissed, nil out the reference.
In your app delegate's applicationWillResignActive: or applicationDidEnterBackground: method, call the dismissWithClickedButtonIndex:animated: method on the reference to the alert view. This would take care of dismissing it on pressing the "home" button.
Keep in mind that applicationWillResignActive: will be called for things such as phone calls, so you need to decide if you'd like to dismiss the alert in cases like that or if you should keep it up through the phone call.
I am building a converter app. In the main screen I have a text field to input numbers and below the text field a picker view will allow users to select conversion parameters, (for example kg to g).
I can hide the keyboard when user click the background by using the following method
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.enterInput resignFirstResponder];
but when I touch the picker view the keyboard is not hiding.
My question is how to dismiss the keyboard when a user touches the picker view.
Got a solution
1) First Create a hidden roundRect botton and change the type to custom (fit the size of the picker).
2) Create a touch up inside action
- (IBAction)hiddenButtonToHideKeyboard:(id)sender {
[self.enterInput resignFirstResponder];
}
3) Create a keyboard appear notification
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(onKeyboardAppear:) name:UIKeyboardWillShowNotification object:nil];
4) Create a keyboard disappear notification
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(onKeyboardHide:) name:UIKeyboardWillHideNotification object:nil];
5) Make the button visible when keyboard is appeared
-(void)onKeyboardAppear:(NSNotification *)notification
{
hiddenButtonToHideKeyboard.hidden=NO;
}
6) Hide the button when the keyboard is disappeared
-(void)onKeyboardHide:(NSNotification *)notification
{
hiddenButtonToHideKeyboard.hidden=YES;
}
5) done
I dont think it is a perfect solution but it works for me :)
I use this in my code. I climb the responder chain and reassign the responder. I put this in my method that shows the pickerView. So far no unexpected issues. Seems to work if a keyboard was showing and seems not to crash if there wasn't a keyboard showing.
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
Here you go. UIPickerViews are a fairly complex system of nested UIViews which is why you weren't getting any response from the touchesBegan:withEvent: method. What you can do is create your UIPickerView subclass as follows:
//
// MyPickerView.h
//
#import <UIKit/UIKit.h>
// Protocol Definition that extends UIPickerViewDelegate and adds a method to indicate a touch
#protocol MyPickerViewDelegate <UIPickerViewDelegate>
// This is the method we'll call when we've received a touch. Our view controller should implement it and hide the keyboard
- (void)pickerViewDidReceiveTouch:(UIPickerView *)pickerView;
#end
#interface MyPickerView : UIPickerView
// We're redefining delegate to require conformity to the MyPickerViewDelegate protocol we just made
#property (nonatomic, weak) id <MyPickerViewDelegate>delegate;
#end
//
// MyPickerView.m
//
#import "MyPickerView.h"
#implementation MyPickerView
#synthesize delegate = _myPickerViewDelegate; // We changed the data type of delegate as it was declared in the superclass so it's important to link it to a differently named backing variable
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// We make sure to call the super method so all standard functionality is preserved
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView) {
// This will be true if the hit was inside of the picker
[_myPickerViewDelegate pickerViewDidReceiveTouch:self];
}
// Return our results, again as part of preserving our superclass functionality
return hitView;
}
#end
Then in your ViewController change it to conform to <MyPickerViewDelegate> instead of <UIPickerViewDelegate>. This is ok since MyPickerViewDelegate inherits from UIPickerViewDelegate and will pass through the standard UIPickerViewDelegate methods.
Finally implement pickerViewDidReceiveTouch: in your view controller:
- (void)pickerViewDidReceiveTouch:(UIPickerView *)pickerView {
[enterInput resignFirstResponder];
}