iPhone MotionShake Stops Working After Clicking Link - iphone

I have an app that displays a UIWebView after the user shakes the phone. It works perfectly until the user clicks a link in the webview. Once the user clicks a link, they must click white space in the screen to get the shake function to start working again.
As you can see below, I have included both the canBecomeFirstResponder, and the webViewDidFinishLoad functions. Please help!
#import "phonetiltViewController.h"
#implementation phonetiltViewController
-(void)viewDidAppear:(BOOL)animated {
[self becomeFirstResponder];
}
-(void)viewDidDisappear:(BOOL)animated {
[self resignFirstResponder];
}
- (BOOL)canBecomeFirstResponder
{ // Shake gesture pops to top. If you don't want this override and
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.subtype == UIEventSubtypeMotionShake) {
[webView loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString:#"http://www.google.com"]]];
}
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self becomeFirstResponder];
}

Then try UIAccelerometer. You can use its delegate method like this
- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration{
if(acceleration.x>1.5 || acceleration.y>1.5 || acceleration.z>1.5){
//load page}

Related

IOS - How to avoid keyboard from hiding when uiwebview reloads

I have a webview that loads a web chat client.
As every chat, the page has a textfield to input text.
The problem is that when the user opens the keyboard it is automatically hidden after a short time due to several ajax requests that are reloading the page. This becomes really annoying for the user as he or she can't input a complete sentence before the keyboard hides.
I don't know why, this only happens in iPhone 4S and iPhone 5. In iPhone 4, 3GS, and Simulator everything works ok.
I have tried to use shouldStartLoadWithRequest to catch the request and load it after the user hides the keyboard, but this ruins the chat session.
I tried to "hang" the request with a Thread sleep in the same method, but it happens in the Main Thread so it freezes the entire application.
Is there a way I can simply avoid the keyboard from hiding?
So after a long research I found a way to do it, its not the best but it helped me a lot.
First use DOM to check if the webView firstResonder
- (BOOL)isWebViewFirstResponder
{
NSString *str = [self.webView stringByEvaluatingJavaScriptFromString:#"document.activeElement.tagName"];
if([[str lowercaseString]isEqualToString:#"input"]) {
return YES;
}
return NO;
}
Then respond to UIWebViewDelegate method shouldStartLoadWithRequest, and return NO if UIWebView is first responder
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if([self isWebViewFirstResponder] &&
navigationType != UIWebViewNavigationTypeFormSubmitted) {
return NO;
} else {
return YES;
}
}
You can use Notification center inside your DidLoad method to listen when the keyboard will hide like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide)
name:UIKeyboardWillHideNotification
object:nil];
- (void) keyboardWillHide{
[webView becomeFirstResponder];
}
which will make the web view first responder and show the keyboard again. I haven't tried it myself so hopefully it will do the trick..
if your text field is on UIView
You can use web view Delegate method
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
[textField becomeFirstResponder];
}
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[textField becomeFirstResponder];
}
otherwise if textField is on UIWebView then replace textField with webView as shown below.
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
[webView becomeFirstResponder];
}
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[webView becomeFirstResponder];
}

How to intercept long press on UITextView without disabling context menu?

I want to intercept long press on UITextview, but don't want to disable the context menu option at the same time.
If I use gesture recognizer on textview, it will disable context menu so I am using the method like below now.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
//fire my method here
}
But, it only fires the method when context menu shows up after the user long press some words. So when the user long press a blank space, then only the magnifying glass shows up, I can't fire the method at the time.
Does anyone have better ideas? Thanks!
//////The Problem Solved//////
Thanks to #danh and #Beppe, I made it even with tap gesture on UITextView. I wanted to show the font bar on textview by long press.
#First, I subclassed the UITextview.
#interface LisgoTextView : UITextView {
BOOL pressing_;
}
#property (nonatomic) BOOL pressing;
#end
#interface LisgoTextView (private)
- (void)longPress:(UIEvent *)event;
#end
#implementation LisgoTextView
#synthesize pressing = pressing_;
//--------------------------------------------------------------//
#pragma mark -- Long Press Detection --
//--------------------------------------------------------------//
- (void)longPress:(UIEvent *)event {
if (pressing_) {
//post notification to show font edit bar
NSNotification *fontEditBarNotification = [NSNotification notificationWithName:#"fontEditBarNotification"
object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotification:fontEditBarNotification];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self performSelector:#selector(longPress:) withObject:event afterDelay:0.7];
pressing_ = YES;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
pressing_ = NO;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
pressing_ = NO;
}
#I used the delay to solve the conflict with tap gesture I implemented on UITextView.
- (void)tapGestureOnTextView:(UITapGestureRecognizer *)sender {
//cancel here if long press was fired first
if (cancelTapGesture_) {
return;
}
//don't fire show font bar
cancelShowFontBar_ = YES;
[self performSelector:#selector(enableShowFontBar) withObject:nil afterDelay:1.0];
//method here
}
- (void)showFontEditBar {
//cancel here if tap gesture was fired first
if (cancelShowFontBar_) {
return;
}
if (fontEditBarExists_ == NO) {
//method here
//don't fire tap gesture
cancelTapGesture_ = YES;
[self performSelector:#selector(enableTapGesture) withObject:nil afterDelay:1.0];
}
}
- (void)enableTapGesture {
cancelTapGesture_ = NO;
}
- (void)enableShowFontBar {
cancelShowFontBar_ = NO;
}
I usually avoid subclassing unless the docs explicitly suggest, this worked for me. Long press and context menu. Whoops - Answer just loaded by #Beppe. Great minds think alike :-)
#interface TextViewSubclass ()
#property(assign,nonatomic) BOOL pressing;
#end
#implementation TextViewSubclass
#synthesize pressing=_pressing;
- (void)longPress:(UIEvent *)event {
NSLog(#"long press");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
self.pressing = YES;
[self performSelector:#selector(longPress:) withObject:event afterDelay:2.0];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
self.pressing = NO;
}
#end
This worked for me. I just wanted to control what happens when the user taps or long presses a link
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
// check for long press event
BOOL isLongPress = YES;
for (UIGestureRecognizer *recognizer in self.commentTextView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
if (recognizer.state == UIGestureRecognizerStateFailed) {
isLongPress = NO;
}
}
}
if (isLongPress) {
// user long pressed a link, do something
} else {
// user tapped on a link, do something
}
// return NO cause you dont want the normal behavior to occur
return NO;
}
Adaptation of Lucas Chwe's answer to work on iOS 9 (and 8). See comment in his answer.
The gist: invert the logic by starting with "no long press", then switch to "long press detected" if at least one UILongPressGestureRecognizer is in Began state.
BOOL isLongPress = NO;
for (UIGestureRecognizer *recognizer in textView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
if (recognizer.state == UIGestureRecognizerStateBegan) {
isLongPress = YES;
}
}
}
Still a bit hackish, but working for iOS 8 and 9 now.
This is a little tricky, but it works for me.
I add a subclass of UIButton on top of my UITextView, check for long touches and pass them to UITextView this way:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
isLongTouchDetected = NO;
[self performSelector:#selector(longTouchDetected) withObject:nil afterDelay:1.0];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (isLongTouchDetected == YES) {
[super touchesCancelled:touches withEvent:event];
} else {
[super touchesEnded:touches withEvent:event];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(longTouchDetected) object:nil];
}
}
- (void)longTouchDetected {
isLongTouchDetected = YES;
// pass long touch to UITextView
}
For SWIFT [Easiest Way]
extension UITextField {
override public func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
if action == "paste:" {
return false
}
return super.canPerformAction(action, withSender: sender)
}
override public func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.isKindOfClass(UILongPressGestureRecognizer) {
gestureRecognizer.enabled = false
}
super.addGestureRecognizer(gestureRecognizer)
return
}
}
Above code has also function for disable "PASTE" option in content menu.
Could you use the textViewDidChangeSelection method of the UITextViewDelegate protocol?

How can I dismissModalViewController when a user hits GO?

I have a view and a button. When the button is pushed, I present a modal view controller that has a uiwebview, and a navigation bar at the top. The hardcoded uiwebview shows a login prompt. Once the user goes in and enters the correct login credentials and hits GO on the keyboard, I not only need it to authenticate me into the server, but also dismiss the modal view controller.
Note the text field I enter data into is part of the website loaded in uiwebview...
When I hit enter now, the web page authenticates, and everything is peachy, but I just need a way to tie in a
[self dismissModalViewControllerAnimated:YES];
command when they hit GO...any thoughts?
Thanks
EDIT
Here is the code that I am implementing in the loginView:
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [[NSURL alloc] initWithString: #"http://website"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: url];
[webView loadRequest: request];
[request release];
[url release];
}
-(BOOL) textFieldShouldReturn:(UITextField *)textField{
[self dismissModalViewControllerAnimated:YES];
return YES;
}
-(IBAction) done{
[self dismissModalViewControllerAnimated:YES];
}
First determine the URL the webpage takes you to on successful login, let's say it's http://website/logedin. Implement UIWebViewDelegate, and detect when a request is made to that address:
- (BOOL)webView:(UIWebView *)wv shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if ([[request.URL absoluteString] isEqualToString:#"http://website/logedin"]) {
[self dismissModalViewControllerAnimated:YES];
return NO;
}
return YES;
}
you can try with below sample code;
#interface WebViewController : UIViewController {
}
UIWebViewDelegate having few delegate methods, you have to implement it in WebViewController.m (for eg).
-(void) webViewDidStartLoad:(UIWebView *)webView{
}
-(void) webViewDidFinishLoad:(UIWebView *)webView{
-(void) webViewDidFinishLoad:(UIWebView *)webView
}
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
}
Before this, you have to set the delegate for UIWebView from Interface Builder.

how to recognise Single tap using UIWebView

I am using UIWebView to show a pdf file in my MainViewController. On single tap I want to load a new View to the MainViewController.
But the UIWebView is not allowing default UITouch event
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
How to solve this problem?
Regards
UIWebView a is subclass of UIScrollView.
You can use touchesShouldBegin:withEvent:inContentView: method defined in UIScrollView documentation.
You need to define this method in your UIWebView own class:
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view {
// DO SOMETHING
return NO;}
#Pierre
Thanks
I did the following :
#implementation UIViewTouch
. . .
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
// DO SOMETHING
return NO;
}
In the view controller implementation:
-(void)viewDidLoad
{
CGRect frame = CGRectMake(0, 0, 500, 1000);
UIViewTouch *viewTouch = [[UIViewTouch alloc]initWithFrame:frame];
NSString *urlAddress = [[NSBundle mainBundle] pathForResource:#"page1" ofType:#"pdf"];
NSURL *url = [NSURL fileURLWithPath:urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[viewTouch loadRequest:requestObj]; [self.view addSubview:viewTouch];
}
Create a UIView sub class containing the whole UIWebView. Then the event hitTest will be fired on touching the webview.
Note: Don't put anything to the container except the webView.
#interface myWebViewContainer : UIView
... ...
#end
Then override the hitTest event:
-(UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(#"Hit test %#", [event description]);
UIView * returnView = [super hitTest:point withEvent:event];
[delegate userDidTapWebView];// notify the delegate that UIWebView is being touched by some naughty user :P
return returnView;
}
Ideally webView userInteractionEnabled should be set to NO and on top of the web view that is the view controller. We can use a TapGesture which will load another view.

Handling touches inside UIWebview

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';"];
}