So I wanted to make my UIWebview respon to touch events. I have read several topics about this. I created a Subclass of UIWebView, added it so my UIViewController and linked it via IB.
In my Subclass, i overrode touches ended and called [super touchedEnded]. didnt help.
I overrode hittest, but it doesnt call super either!
If I add this code to my subclassed UIWebView:
#implementation UIWebView (CustomView)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"hit");
return self;
}
#end
then the touches ar forwarded to the superclass, but hittest is not(!)and additionally the webview stops scrolling.
Well, I want so recognize a touch in my webview and pass it to super - what am I doing wrong?
EDIT:
I'm wondering, why so many suggestions include using 'hittest' - this results in being unable to scroll your webview.....any ideas?
Answering my own question here - this is the version that did what I wanted: The right way. This is about subclassing UIWindow and passing on the events caught.
I would still love some explanations to my previous questions tho :P
Related
When the app is launched, I want to freeze UI for a second or so to get a precondition service (for example - location service) initialized. Is there a way to do that?
Just place a transparent UIView on top of the entire screen and have it intercept all touch events.
To intercept touch events, simply subclass a UIView and override
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Usually inside that method you'd call super on it, but in this case you would just return YES:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return YES;
}
If you'd like to be really fancy you can add a synthesized property to the UIView subclass called shouldInterceptTouches and do something like this:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return shouldInterceptTouches ? YES : [super pointInside:point withEvent:event];
}
Also remember to make sure that your transparent view is above the other views.
Side-note: Apple's Human Interface Guidelines don't like it when you do stuff like that. A user will be confused and disappointed if your app is not responsive and it may cause them to quit the app if they think it's frozen. You're better off displaying some sort of UIActivityIndicatorView and disabling only the buttons absolutely necessary. Apple likes it when you do stuff like that in the background and allow the user to do other things, just in case it takes a while or fails.
You could use a Modal View or disable all components until the location service has finished.
This is the responsibility of the application not the OS or the frameworks.
So I just have a standard UIViewController with a UIWebView in it that displays a pdf. For the app functionality, I have need to be able to respond to the UIWebView's nested UIScrollView events like scrollViewWillBeginDragging, scrollViewDidScroll, etc.
The only way I can get access to the scrollView is to (it seems like a hack) go in and get it by the subviews array:
for (id subview in webView.subviews){
if ([[subview class] isSubclassOfClass: [UIScrollView class]]) {
UIScrollView * s = (UIScrollView*)subview;
s.delegate = self;
s.tag = 1;
scrollView = s;
}
}
But that seems to introduce more problems than it's worth, because I lose native UIScrollView stuff like zooming.
So to sum up what I'm needing:
What is the best way to set my UIViewController class as the delegate of the UIScrollView inside of the UIWebView? Is there something I need to do with subclassing my own UIWebView so that it handles events from it's built in UIWebView a certain way and then pass it along somehow to the UIViewController? How does that passing thing work anyway?
Please advise!
Thank you!
Have you checked there is only one UIScrollView subclass in the subviews? Bung in a log in your loop to see. If there is more than one, then you'll only pick up the last one using your code.
If there is just one UIScrollView subclass, you could try saving a reference to its delegate and then in your own delegate methods passing messages on after you have done your business.
So, in your loop, something like
originalDelegate = s.delegate
And then for the delegate methods, something like:
- (void) scrollViewDidScroll: (UIScrollView*) scrollView;
{
// do your stuff
[originalDelegate scrollViewDidScroll: scrollView];
}
You might need to check whether originalDelegate responds to the selector before calling it, i.e. if ([originalDelegate respondsToSelector: #selector(scrollViewDidScroll:)) etc. If it were me, I'd start by implementing all twelve delegate methods defined in the UIScrollView delegate protocol.
Not tested this, so will be interested to know if it can be made to work. Do note, the docs explicitly say that UIWebView "should not be subclassed"
I have read some other articles like here and here but unfortunately there are some differences in my code that won't make these work (so please don't suggest these answers).
I would like to dismiss the keyboard when the user taps the background. Normally this would be easy, except that my UITextField objects are inside a UIScrollView which makes it so I can't catch the touch events (the UIScrollView swallows them so they don't reach the base view). One way to get around this is to register for a generic gesture (a tap), but this catches all taps, including the ones intended for the submit button.
So basically, 'touchesBegan:withEvent:' wont work because it never gets called, and gestures wont work because they dont account for button presses.
Here's the question: is there some way to detect a simple tap on a UIScrollView? Once I detect the tap I know how to do the rest. Thanks!
You can't use touchesBegan:withEvent on the superview of the scrollview, but what about subclassing UIScrollView and handling the touch there? You can then proceed normally with a call to super's implementation to keep from stepping on the UIScrollView's toes:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Insert code to dismiss keyboard if needed */
// This makes sure scrolling proceeds normally.
[super touchesBegain:touches withEvent:event];
}
I also ran into same problem. While in IOS 6.0, it was working fine, but as soon i switched to IOS 5.0, it started to show the same behaviour you have mentioned.
Workout for IOS 5.0 - You should use the UITapGestureRecognizer. Then set its delegate to self and implement the following delegate method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
as
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return ! ([touch.view isKindOfClass:[UIControl class]]);
}
#end
above code will verify that the object under touch is not a UIButton or any control element, then only handle the touch
I hope it would solve your problem.
I added a ViewController to an app for ad banner support. The app was based on a UINavigationController so I added the ViewController as a subview to the UINavigationController. Now the area of the ad banner does not recognize when it is touched to open the ad in safari. I have tried to make the ViewController becomeFirstResponder but that doesn't work. Does anyone have any thoughts on how to fix?
you say you added it as a subview, but I hope you mean you pushed it with
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
Your view controller can respond to events because it is a UIResponder. UIView and therefore most visual controls like UIButton, UISlider etc. inherit from UIResponder also - but they implement specific actions based on user interaction.
If you really want to intercept events in a UIViewController you will need to handle touchesBegan/Moved/Ended events. More likely you have at least a UIWebView or UIImageView in the view controller if it is displaying something you want to click on.
If UIWebView you'll need a UIWebViewDelegate implementing
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
will let you know what links are being opened.
If a UIImageView you will also need to set the property userInteractionEnabled before you see any touch events.
If you're dealing with iAd's AdBannerView class, that inherits from UIView also, so comments for UIImageView apply. HOWEVER ADBannerViewDelegate is a requirement for built-in interaction support - just like UIWebViewDelegate, you must implement a permission/notification function,
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
It appears that all the touch methods of a UIView are only called if the touches began within the bounds of that view. Is there a way to have a view respond to a user who has touched outside the view, but then dragged his fingers into the view?
In case it matters, my specific application is for dragging a MKPinAnnotationView (using built-in 4.0 dragging). I want something to happen if the user drags a pin onto another view (which happens to be an AnnotationView as well, but it could be anything). No method for dragging is called until I let go of the pin; and no method no the UIView that's being dragged to seems to be called unless I started by touching from within the view.
Because the superview is a MKMapView, it is difficult to just use the touchesMoved event of that and check if the user is in the right location or not. Thanks!
So after playing around with it for a while, I found that the answer given here actually gave me what I needed, even though the question being asked was different.
It turns out you can subclass UIGestureRecognizer; and have it handle all the touches for the view that it has been added to (including an MKMapView). This allows all the normal MKMapView interactions to still behave without any problem; but also alerts me of the touches. In touchesMoved, I just check the location of the touch; and see if it is within the bounds of my other view.
From everything I tried; this seems to be the only way to intercept touchesMoved while the user is dragging an MKAnnotation.
You sure can:
(HitstateView.h)
#import <UIKit/UIKit.h>
#interface HitstateView : UIView {
id overrideObject;
}
#property (nonatomic, retain) id overrideObject;
#end
(HitstateView.m)
#import "HitstateView.h"
#implementation HitstateView
#synthesize overrideObject;
- (void)dealloc {
self.overrideObject = nil;
[super dealloc];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self) {
return overrideObject;
}
return hitView;
}
#end
Make this view the size of your touch area. Set the overideObject to the view you want the touches to go. IIRC it ought to be a subview of the HitstateView.
Every view inherits UIResponder so every view gets touchesBegan/Moved/Ended - I do not think starting the touch outside the view means the view gets no event when the touch moves over the view. If you want to get a notification that something has been dragged onto your MKMapView you should make a subclass that handles the touch but then passes the event to super, allowing the hierarchy to do whatever it needs to do with the touch. You don't need to capture or modify the event just observe it.
It depends on how your views are set up. Generally leveraging the responder chain is the best way to go. It allows you to play tricks, though it may be too specific to address your particular needs.
You can also play tricks with forward events by override hit testing:
http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/MultitouchEvents/MultitouchEvents.html%23//apple_ref/doc/uid/TP40009541-CH3-SW3
Your particular case sounds pretty exotic, so you may have to play tricks like having a parent view whose frame is large enough to contain both views in question.