I have this inside my viewController:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventSubtypeMotionShake) {
NSLog(#"I have shaked");
}
}
Why is this not working?
Edit:
I do infact have this:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
If the UIViewController is loaded at the very start of the application, I've seen an odd glitch in OS 3.0 where it would not become the first responder unless you delayed the appropriate message a bit. Try placing
[self performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.3];
within -loadView or something else that is called when the controller is first set up.
There may be a more elegant way to work around this, but this approach has worked for me.
The viewController must be the first responder during the shake to receive this event.
This could be one reason it's not working.
Related
I'm having problem to control the iPhone controls with my avplayer.
if I put the function
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
in the view controller that responsible for playing the function called but only if I i'm going to background in the current view controller.
if i'm going to background from other view controller the function never called.
that's why i want to put it in the app delegate.
I tried Becomefirstresponse and to put the function in every view controller but it did help.
also I call
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
in the
-(void)applicationWillResignActive:(UIApplication *)application
thanks
I have used below code to iPhone Control -
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
Used to get register for listening the remote control.
Once done remove it -
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
make the App canBecomeFirstResponder-
- (BOOL)canBecomeFirstResponder {
return YES;
}
Used delegate method to handle iPhone control, like play and pause while doble tap on the home button
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
//if it is a remote control event handle it correctly
if (event.type == UIEventTypeRemoteControl) {
if (event.subtype == UIEventSubtypeRemoteControlPlay) {
[audioPlayer play];
NSLog(#"play");
} else if (event.subtype == UIEventSubtypeRemoteControlPause) {
[audioPlayer stop];
NSLog(#"pause");
} else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause) {
NSLog(#"toggle");
}
}
}
In my case i am able to handle play and pause.Please let know if any thing wrong.
You can move the function up the responder chain, to UIApplication subclass. This way, it will always be there to catch the event.
This kind of event is ignored in common UI and controller classes, so it travels up to the bottom of responder chain, where your app delegate and the the application itself reside.
As noted here, UIApplication's delegate is not part of responder chain (I was wrong here). UIApplication is there, so is root UIWidow, all the views in chain and corresponding UIViewControllers.
Hint: becomeFirstResponder must be called from within viewDidAppear, not viewWillAppear...
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
I'm trying to make a shake events.
I tried:
1) How do I detect when someone shakes an iPhone? (posts of Kendall, and Eran)
2) motionBegan: Not Working
but nothig helps.
My View becomes first responder, but motionBegan/motionEnded never called.
Is there some additiol settings must be done, or i'm missing somethig? My iOS SDK is 4.3.
I have a class of UIView:
#import "ShakeView.h"
#implementation ShakeView
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
NSLog (#"123");
if ( event.subtype == UIEventSubtypeMotionShake ) {
NSLog(#"Shake!");
}
if ([super respondsToSelector:#selector(motionEnded:withEvent:)]) {
[super motionEnded:motion withEvent:event];
}
}
#end
In my ViewController's xib class of View is ShakeView.
my ViewController pushed:
Wheel *secondViewController = [[Wheel alloc] initWithNibName:#"Wheel" bundle:nil];
[self.navigationController pushViewController:secondViewController animated:YES];
[secondViewController release];
In my ViewController:
- (void) viewDidAppear:(BOOL)animated
{
[self.view becomeFirstResponder];
[super viewWillAppear:animated];
NSLog(#"%d", [self.view isFirstResponder]);
}
- (void) viewWillDisappear:(BOOL)animated
{
[self.view resignFirstResponder];
[super viewWillDisappear:animated];
}
It logs "1", so it IS first responder. But it logs nothing else.
I spend a half day on this few lines of code, and I have no more ideas. Do anyone knows how to solve it?
Thanks.
This is much too late to help SentineL, but I was having the same problem and I like his question because it is clear that he has all the relevant code in place -- except one crucial line, in the application delegate's didFinishLaunching:
[self.window makeKeyAndVisible];
This is very hard to debug, because even without this line, everything else will be fine. Your gestures will work, your controls will respond, you will be able to make your view first responder (as SentineL checked) -- but your subclassed window or view or view controller will never receive the motion events.
Which doesn't make sense to me. Why would makeKeyAndVisible affect the accelerometer but not gestures? Hopefully some more experienced user can answer that.
P.S. If you use this code as an example, I would recommend that you omit the super respondsToSelector conditional. Of course it responds to the selector; you're overriding it.
I have UIViewControllerand UITableView as child in the view,
what I want to do is when I touch any row I am displaying a view at bottom. I want to hide that view if the user touch any where else then rows or the bottomView.
The problem is when I click on UITableView it doesn't fires touchesEnded event.
Now how can I detect touch on UITableView and distinguish it with row selection event.
Thanks.
No need to subclass anything, you can add a UITapGestureRecognizer to the UITableView and absorb the gesture or not depending on your criteria.
In your viewDidLoad:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapOnTableView:)];
[self.myTableView addGestureRecognizer:tap];
Then, implement your action like this for the criteria:
-(void) didTapOnTableView:(UIGestureRecognizer*) recognizer {
CGPoint tapLocation = [recognizer locationInView:self.myTableView];
NSIndexPath *indexPath = [self.myTableView indexPathForRowAtPoint:tapLocation];
if (indexPath) { //we are in a tableview cell, let the gesture be handled by the view
recognizer.cancelsTouchesInView = NO;
} else { // anywhere else, do what is needed for your case
[self.navigationController popViewControllerAnimated:YES];
}
}
And note that if you just want to simply pick up clicks anywhere on the table, but not on any buttons in cell rows, you need only use the first code fragment above. A typical example is when you have a UITableView and there is also a UISearchBar. You want to eliminate the search bar when the user clicks, scrolls, etc the table view. Code example...
-(void)viewDidLoad {
[super viewDidLoad];
etc ...
[self _prepareTable];
}
-(void)_prepareTable {
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.allowsSelection = NO;
etc...
UITapGestureRecognizer *anyTouch =
[[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(tableTap)];
[self.tableView addGestureRecognizer:anyTouch];
}
// Always drop the keyboard when the user taps on the table:
// This will correctly NOT affect any buttons in cell rows:
-(void)tableTap {
[self.searchBar resignFirstResponder];
}
// You probably also want to drop the keyboard when the user is
// scrolling around looking at the table. If so:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self.searchBar resignFirstResponder];
}
// Finally you may or may not want to drop the keyboard when
// a button in one cell row is clicked. If so:
-(void)clickedSomeCellButton... {
[self.searchBar resignFirstResponder];
...
}
Hope it helps someone.
You should forward the touch event to the view's controller.
Subclass your tableview control and then override the method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event]; //let the tableview handle cell selection
[self.nextResponder touchesBegan:touches withEvent:event]; // give the controller a chance for handling touch events
}
then , you can do what you want in the controller's touch methods.
I just stumbled onto what may be a solution for your problem. Use this code when you create your table view:
tableView.canCancelContentTouches = NO;
Without setting this to NO, the touch events are cancelled as soon as there is even a slight bit of vertical movement in your table view (if you put NSLog statements in your code, you'll see that touchesCancelled is called as soon as the table starts scrolling vertically).
I was facing the problem since a long time and didn't got any working solution. Finally I choose to go with a alternative. I know technically this is not the solution but this may help someone looking for the same for sure.
In my case I want to select a row that will show some option after that I touch anywhere on table or View I want to hide those options or do any task except the row selected previously for that I did following:
Set touch events for the view. This will do the task when you touch anywhere on the view except the table view.
TableView's didSelectRowAtIndexPath do following
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
if(indexPath.row != previousSelectedRow && previousSelectedRow != -1) {
// hide options or whatever you want to do
previousSelectedRow = -1;
}
else {
// show your options or other things
previousSelectedRow = indexPath.row;
}
}
I know that this is older post and not a good technical solution but this worked for me. I am posting this answer because this may help someone for sure.
Note: The code written here may have spell mistakes because directly typed here. :)
Try this methods:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
}
Use scrollViewDidEndDragging like alternative of touchesEnded. Hope it helps.
To receive touch events on the UITableView use:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//<my stuff>
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
//<my stuff>
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
//<my stuff>
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
//<my stuff>
[super touchesCancelled:touches withEvent:event];
}
In your controller class declare a method which removes the bottom view. Something like this:
-(IBAction)bottomViewRemove:(id)sender {
[bottomView removeFromSuperview];
}
In Interface Builder, select your view and in the identity inspector in the custom class section, change the class from UIView to UIControl. After that go to the connections inspector and connect the TouchUpInside event to the method declared above. Hope this helps.
How do I use shake-events in iOS 4.0+? I tested this solution but isn't working in 4.1. Is it possible that something changed or am I doing someting wrong? motionBegan: Not Working
From this SO question How do I detect when someone shakes an iPhone?
The main trick is that you need to have some UIView (not view controller) that you want as firstResponder to receive the shake event messages. Here's the code that you can use in any UIView to get shake events:
#implementation ShakingView
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if ( event.subtype == UIEventSubtypeMotionShake )
{
// Put in code here to handle shake
}
if ( [super respondsToSelector:#selector(motionEnded:withEvent:)] )
[super motionEnded:motion withEvent:event];
}
- (BOOL)canBecomeFirstResponder
{ return YES; }
#end
You can easily transform any UIView (even system views) into a view that can get the shake event simply by subclassing the view with only these methods (and then selecting this new type instead of the base type in IB, or using it when allocating a view).
In the view controller, you want to set this view to become first responder:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
Don't forget that if you have other views that become first responder from user actions (like a search bar or text entry field) you'll also need to restore the shaking view first responder status when the other view resigns!
This method works even if you set applicationSupportsShakeToEdit to NO.
I have created a subclass of UIWebView , and have implemented the
touchesBegan, touchesMoved and touchesEnded methods.
but the webview subclass is not handling the touch events.
Is there any method to handle the touch events inside the UIWebView subclass ???
No subclassing needed, just add a UITapGestureRecognizer :
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapMethod)];
[tap setNumberOfTapsRequired:1]; // Set your own number here
[tap setDelegate:self]; // Add the <UIGestureRecognizerDelegate> protocol
[self.myWebView addGestureRecognizer:tap];
Add the <UIGestureRecognizerDelegate> protocol in the header file, and add this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
If all you need is to handle gestures, while leaving the rest of the UIWebView functionality intact, you can subclass UIWebView and use this strategy:
in the init method of your UIWebView subclass, add a gesture recognizer, e.g.:
UISwipeGestureRecognizer * swipeRight = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(handleSwipeGestureRightMethod)];
swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
[self addGestureRecognizer:swipeRight];
swipeRight.delegate = self;
then, add this method to your class:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Add and handle your designated selector to the class, in this case "handleSwipeGestureRightMethod" and you are good to go...
You could put an UIView over your UIWebView, and overide the touchesDidBegin etc, then send them to your webview. Ex:
User touches your UIView, which provokes a
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Execute your code then send a touchesBegan to your webview like so:
[webView touchesBegan:touches withEvent:event];
return;
}
your UIView has to be over the webview.
I'm not sure if this is what you want (it's not what you asked for, but it might work depending on what your end game is), but you could instead interpret the touches in JavaScript from inside the UIWebView, and get javascript to do
document.location='http://null/'+xCoord+'/'+yCoord; // Null is arbitrary.
Then you can catch that using the UIWebView's delegate method
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
And if the request.URL.host (or whatever it is) isEqualToString:#"null" take the relevant action (and return NO instead of YES). You can even add the JS to each page by doing something like:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[webView stringByEvaluatingJavaScriptFromString:#"window.ontouchstart=function(/* ... */);"];
}
Hope this helps?
Handling gestures on a UIWebView is discussed in this Apple Developer forum thread.
Using the info given there, there will be no need for an extra view in most or all cases, and as mentioned here before, overriding UIWebView is not the way to go.
Copypaste of the most important post in the thread:
This is a known issue. The UIWebView has its own UITapGestureRecognizers, and they're on a private subview of the UIWebView itself. UIGestureRecognizer precedence defines that gestures attached to views deeper in the view hierarchy will exclude ones on superviews, so the web view's tap gestures will always win over yours.
If it's okay in your case to allow your tap to happen along with the web view's normal tap your best solution would be to implement the UIGestureRecognizerDelegate method gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer and return YES for other tap gestures. This way you'll get your tap handler called, and the web view will still get its called.
If you need to be the only one handling the tap you'll have to subclass UITapGestureRecognizer so you can use the one-way overrides in UIGestureRecognizerSubclass.h, an you can then return NO from canBePreventedByGestureRecognizer: when asked if the web view's tap gesture recognizer can prevent yours.
In any case, we know about this and hope to make it easier in the future.
I've just found that UIWebView does check whether it responds to the - (void)webViewDidNotClick: (id)webBrowserView selector, once one taps on the view area (not on hyperref, or any other area that should be handled specifically). So you may implement that selector with your handling code :)
Do you mean your sub-classed implementation is not called when touchesBegan, touchesMoved and touchesEnded are called?
It sounds like a problem with how you've created an instance of the object. More details are required I think.
(taken form comments)
Header File
#import <UIKit/UIKit.h>
#interface MyWebView : UIWebView { } #end
Implementation File
#import "MyWebView.h"
#implementation MyWebView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) { } return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"MyWebView is loaded");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touches began");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touches ended");
}
- (void)dealloc {
[super dealloc];
}
#end
I would try overriding -sendEvent: on UIWindow, to see if you can intercept those touch events.
Following on from what Unfalkster said, you can use the hitTest method to achieve what you want, but you don't have to subclass UIWindow. Just put this in your web view subclass. You will get a compile time warning but it does work:
- (void)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
// get location info
CGFloat x = point.x;
CGFloat y = point.y;
// get touches
NSSet *touches = [event allTouches];
// individual touches
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan) {
// touches began
} else if (touch.phase == UITouchPhaseMoved) {
}
// etc etc
}
}
// call the super
[super hitTest:point withEvent:event];
}
Hope that helps!
If you want to detect your own taps but disable the UIWebView's taps then you can use my solution:
-(void)recursivelyDisableTapsOnView:(UIView*)v{
for(UIView* view in v.subviews){
for(UIGestureRecognizer* g in view.gestureRecognizers){
if(g == self.ownTapRecognizer){
continue;
}
if([g isKindOfClass:[UITapGestureRecognizer class]] ||
[g isKindOfClass:[UILongPressGestureRecognizer class]] ||
[g isKindOfClass:NSClassFromString(#"UITapAndAHalfRecognizer")]){
g.enabled = NO;
}
}
[self recursivelyDisableTapsOnView:view];
}
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
[self recursivelyDisableTapsOnView:webView];
//disable selection
[webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.style.webkitUserSelect='none';"];
// Disable callout
[webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.style.webkitTouchCallout='none';"];
}