I have a UIWindow with windowLevel set to UIWindowLevelStatusBar+1. This window has a single semi-transparent view that blocks the status bar. I need to sometimes pass touch events from the view on to the status bar. Any ideas?
So, it seems to be doable with a custom subclass of UIWindow overriding hitTest:withEvent: that manually detects a touch in the subview, and always returns nil.
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([event type]==UIEventTypeTouches) {
UIView *v=[super hitTest:point withEvent:event];
if (customSubViewthatCoversStatusBarOnly==v)
//doLotsOfCoolStuff
}
return nil;
}
Status bar recognizes all touches, so there is no breakage with scroll-to-top, return to call, VoiceOver, etc.. And I still get to intercept taps on statusbar.
I hacked this up just now. I will probably upload an update to App Store later this week with a more mature version of this, will see how much complaining Apple will do.
EDIT - 7th April:
Was approved by Apple. Works flawlessly.
You might find this component over on github helpful.
Otherwise, Cocoa with Love blog post is really useful to read perhaps.
As far as I understand this, you should use - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event to implement that behavior. Basically, you either return self if you want to handle the touch event or [super hitTest:point withEvent:event] to let the status bar handle the touch event.
Check out the UIView Class Reference for more.
EDIT: As Jonathan mentioned, Apple might not approve this.
Related
I have tried various solutions provided on this site and others to implement touch events on uiwebview. But still I am not able to do this. Actually, i have created a article reader application. Here, I have added a uiwebview on the normal uiview. Now, I want to trace some user touch events on that particular webview.
When I do this on normal view, it works perfectly. But if I try it on webview. it stops working.
The solutions I tried before are
implementing touch methods like
touchbegan
touchended
touchmoved
touch cancelled
2 implementing uigesturerecognizer
3 implementing window touch events like send event
Now If anyone can help me or tell me where I am doing wrong or a new solution(other than this), then I will be thankful.
Put a transparent UIVIew on top of your UIWebView to capture touches. You can then act on them or optionally pass them down to the UIWebView using the touchesBegan deletage method.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.myUIWebView touchesBegan:touches withEvent:event];
}
Check this previous post for details: iOS - forward all touches through a view
I subclassed UIWebView and just "leeched" onto its gesture recognizers in subviews 2 levels deep (you could go recursively but thats enough for iOS6-7).
Then you can do whatever you want with the touch location and gesture recognizer's state.
for (UIView* view in self.subviews) {
for (UIGestureRecognizer* recognizer in view.gestureRecognizers) {
[recognizer addTarget:self action:#selector(touchEvent:)];
}
for (UIView* sview in view.subviews) {
for (UIGestureRecognizer* recognizer in sview.gestureRecognizers) {
[recognizer addTarget:self action:#selector(touchEvent:)];
}
}
}
What is the coding to detect a "quick touch" on a UITableView for the purpose of toggling to/from full screen?
In particular this is because the user is on a UITableView they would still need the ability to drag the list of items up/down, and potentially click on a cell/row to dig deeper. But if they touched quickly then this could be the trigger to toggle between full-screen mode (i.e. nav bar & tool bar).
Background - When I say toggling to/from full screen I'm refering to carrying out what is described here, however in this answer there was no mention in terms of how to plug this into the callbacks for a UITableView which is being displayed within a UINavigationController stack.
The following code might help you
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
timeStampStart = event.timestamp;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
timeStampEnd = event.timestamp;
touchDuration = timeStampEnd - timeStampStart;
if(touchDuration > smallTimeStamp)
[super touchesEnded:touches withEvent:event];
else
[self zoomMyTableView];
}
Wanted to put up this as a possible answer, the answer being there is no relative straight way to achieve what I've asked for this. That is a way for a tap or double-tap to be detected in a UITableView page, which is already picking up row touches and scroll up/down etc.
Haven't verified whether this is the case or not, but people could up-vote this answer if they believe this to be true. (also waiting on sample from ypk who perhaps has an answer)
Is there a simple way to detect if someone touched a UIScrollView without having to disable user interactions?.
I know this has been answered a few times before, but every answer I find is somebody wanting to detect hits in an image. I don't have an image. What I do have is a scroll view with a number of text fields embedded in it. They used to be inside a UIControl, from which I could detect a touchDown and call resignFirstResponder on all my textfields (for when the keyboard is up). But when they are in a UIScrollView, I can't seem to find a simple way to do this.
I don't really want to have to write code to do the scrolling myself, which is what I assume I have to do if I disable user interactions and grab the touchesBegan message. But I may be wrong. I'm still a little new at this, but this is the first time I haven't been able to figure out the answer by reading the code doc and googling ...
Thanks for any help,
J
Override touchesBegan, do your processing, and call the super implementation so that the scroll view still gets the message.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//call resignFirstResponder
[super touchesBegan:touches withEvent:event];
}
Ok, now I feel a little silly answering this myself. I'm sure the other suggested answer would have worked also, but I found something else while browsing in other topics which totally fixed this for me.
I added this to my viewDidLoad:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(backgroundTap:)];
[scrollView addGestureRecognizer: singleTap];
Where backgroundTap is
- (IBAction)backgroundTap:(id)sender
is the function I was originally calling from touchDown in UIControl to resign all keyboard firstResponders.
This works perfectly and adds very little code. And I DON'T have to disable user interactions! Yay!
Thanks all for your comments and help.
J
I fiddled with this for a while, and found that simple touchesBegan and touchesEnd type stuff didn't really work.
They don't work because because of the gestures. For example touchesEneded never fires when you swipe to scroll (at least not in the UIScrollView). So while you can detect when a touch event starts, you'll never know when it ends.
I wasn't real keen on including a gesture recognizer for simple stuff and discovered that you can find out about the state of the scrolling better from interacting with the UIScrollView directly.
And it's not that painful. Just add the protocol, then add the functions you need for detection.
In the view (class) that contains the UIScrolView, add the protocol, then added any the functions from here to your view (class).
Example:
// --------------------------------
// In the "h" file:
// --------------------------------
#interface myViewClass : UIViewController <UIScrollViewDelegate> // <-- Adding the protocol here
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView;
// --------------------------------
// In the "m" file:
// --------------------------------
#implementation BlockerViewController
UIScrollView *scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"end decel");
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(#"end dragging");
}
// All of the available functions are here:
// https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html
Can we call different actions / delegates in response to the two different events of
A tap on a UIButton
A tap-and-hold on a UIButton
?
Yes, it's reasonably easy to implement this using a UILongPressGestureRecognizer (on iPhone OS 3.2+). A long press will be handled by the gesture recognizer, and a short tap will pass through to the button's normal action.
For example, I subclassed UIButton and added the following method for specifying a long touch action to go along with a tap (longPressGestureRecognizer is an instance variable):
- (void)setLongTouchAction:(SEL)newValue
{
if (newValue == NULL)
{
[self removeGestureRecognizer:longPressGestureRecognizer];
[longPressGestureRecognizer release];
longPressGestureRecognizer = nil;
}
else
{
[longPressGestureRecognizer release];
longPressGestureRecognizer = nil;
longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:[[self allTargets] anyObject] action:newValue];
[self addGestureRecognizer:longPressGestureRecognizer];
}
}
I then could do the following to set both short tap and long press actions that will be handled by the same target:
[undoButton addTarget:self action:#selector(performUndo:) forControlEvents:UIControlEventTouchUpInside];
[undoButton setLongTouchAction:#selector(showUndoOptions:)];
As you can see, this is useful for the undo buttons you see in title bars of many iPad applications.
Brad Larson's answer looks pretty good but here's another one that might give you a bit more flexibility/control of what you want or might want to do.
You subclass UIButton, you override the touchesBegan and touchesEnded methods so that when the user starts a touch you call
[self performSelector:#selector(detecetedLongTap) withObject:nil afterDelay:1.0];
and in the touchesEnded you call:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(detecetedLongTap) object:nil];
to cancel the event if the finger was lifted too soon.
You can get full code for this in this blog post:
http://www.isignmeout.com/adding-long-tap-functionality-uibutton/
The best solution I can think of, is to create another class, and subclass UIButton. Then on Interface Builder (if that's what you're using), you can set the button's class to the custom class you just created.
So in this new class, you have to override a method called
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
This is basically telling you that someone pressed down on your button. The touches is an NSSet and it holds all the information for all the fingers that are pressing down on the screen. If you only are interested in the one that's pressing on the button itself, you'll probably have something like:
NSSet *myTouches = [event touchesForView:self.view];
So now that you have the touches that correspond to your button, you have to find out how many times the user tapped on that button. You do that with something like:
NSUInteger numTaps = [[myTouches anyObject] tapCount];
If this number is 2, that means the user just double tapped your button. Now comes the next part. How do you know if the user is holding that button? Well when the user lets go of the screen, another method gets called. You'll need to override that one as well:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
This is where you know if the person has stopped touching the screen or if his finger is still on it. If his finger is still on it, then this event hasn't been called yet.
Now enough with the background
Here's my suggestion to you. I suggest you override the touchesBegan: method and check if the number of taps in the button is 2. If so, then start a timer that does what you need it to do, for as long as you need it to be done, and then on the touchesEnded: method, you'll go ahead and stop that timer, and this way you will have done whatever it is that you needed to do, for as long as you needed to do it OR as long as the user has held on to the button.
I hope this helps, obviously I didn't write the whole code for you, you'll have to experiment and research that stuff, but if you have any questions, I'll be happy to lend a helping hand :)
I need to fake a real touch event programmatically, so that any subsequent touch (from the users finger, for example) will immediately result into a -touchesMoved.... message rather than -touchesBegan....
Any solution how to do that? I know Three20, but I need something legal.
I'm not sure if I got you right, but if you want touchesBegan act like touchesMoved why don't do like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(specialModeOn)//I don't know if you need this
{
[self touchesMoved:touches withEvent:event];
}
else
{
//whatever touchesBegan should do normally
}
}
Is it not viable to simply set a flag to interpret the next touchesBegan event as a touchesMoved event instead, and have your own code handle figuring out what the latter event would look like?