I'm trying to figure out how to do something perhaps slightly non-standard. I have a view controller (a UIViewController) that has 1 UITextField and 3 UIButtons. I use the UIButtons basically as toggles; only 1 can be selected at a time. So each has:
[myButton1 addTarget:self action:#selector(buttonPushed:)
forControlEvents:UIControlEventTouchUpInside];
And I do this to toggle between the buttons:
- (void) buttonPushed:(id)sender
{
myButton1.selected = NO;
myButton2.selected = NO;
myButton3.selected = NO;
if (sender == myButton1) {
myButton1.selected = YES;
}
else if (sender == myButton2) {
myButton2.selected = YES;
}
else if (sender == myButton3) {
myButton3.selected = YES;
}
}
Now the thing is, I still want the buttons to be selectable even if the text field keyboard is up (the buttons and text field are both visible when the keyboard is shown). But since the text field has first responder the button touches are not recognized (never reaches the target buttonPushed method). I don't want the user to have to dismiss the keyboard just to select a button.
How can I do this? Thanks!
I figured it out. The 2 answers here were correct; the problem is that I was using a UITapGestureRecognizer on this view. For some reason that was preventing any touch events from being reached other than the predefined taps I suppose. Doh.
I removed the gesture recognizer and now the touch events are hit when the keyboard is shown.
I've had no problems getting events from buttons with the keyboard up. Are you sure you're not reaching that handler at all?
Any chance myButton1, etc. are not connected?
i think you should use touch methods like
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
or
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
i prefer u to use touchEnded method. here u check if the user taps on uibutton then do what you want
Related
I want to disable touches on all areas of the screen apart from a specific few points (e.g buttons). I.e. I don't want 'touchesBegan' to trigger at all when I tap anything other than a button. Calling
self.view.userInteractionEnabled = NO;
has the desired effect for not registering touches, but then of course I can't tap any buttons. I basically want the button to still work, even if there are 5 points touching the screen, i.e. all touch inputs have been used up, and the button represents the 6th.
Is this possible?
I've tried inserting a view with userInteraction disabled below my buttons, but it still registers touches when the user taps the screen. It seems the only way to disable touch registering is to do so on the entire screen (on the parent UIView).
UPDATE:
I've tried using gesture recognizers to handle all touch events, and ignore those that don't qualify. Here is my code:
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>
...
- (void)viewDidLoad
{
[super viewDidLoad];
UIGestureRecognizer *allRecognizer = [[UIGestureRecognizer alloc] initWithTarget:self action:nil];
allRecognizer.delegate = self;
[self.view addGestureRecognizer:allRecognizer];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint coords = [touch locationInView:self.view];
NSLog(#"Coords: %g, %g", coords.x, coords.y);
if (coords.y < 200) {
[self ignoreTouch:touch forEvent:nil];
return TRUE;
}
return FALSE;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%i touch(es)", [touches count]);
}
However the screen still 'reads' the touches, so if I place 5 fingers down, the 6th one won't trigger a button press...
You need to set up an invisible UIButton and lay it between the view that should not register touches and the UIButtons that should still be active.
Now you need to set the invisible button's 'userInteractionEnabled':
//userInteractionEnabled == NO => self.view registeres touches
//userInteractionEnabled == YES => self.view doesn't register touches
[_invisibleButton setUserInteractionEnabled:NO];
What really matters in this solution is that both - the invisible and the visible buttons are direct subviews of the VC's view.
You can download an example project from my dropbox:
https://dl.dropboxusercontent.com/u/99449487/DontTapThat.zip
However this example just prevents the handling of certain touches. Completly ignoring input isn't technically possible: Third party apps are not responsible for for detecting input. They are just responsible for handling input. The detection of touch input is done iOS.
The only way to build up a case like you describe it in the comments is to hope that iOS won't interpret the input of your case as a "finger" because it's most likely going to cover an area that's way bigger than a finger.
So in conclusion the best way would be to change the material of the case you're about to build or at least give it a non conductive coating. From a third party developers point of view there is no way to achieve your goals with software if there is a need for 5 fingers as described in the comments.
There is couple of methods in UIView that you can override:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
This should prevent to call touchBegin and other methods.
Regards.
I have a nice solution for this. What ever the area you want to hide the interaction place a transparent button on top of the area.
touchesBegan is a default method so it must call all the time when touch happens on view so there is no way-out, But you can still do one thing set
self.buttonPlayMusic.userInteractionEnabled = FALSE;
for the object you don't need touch may be this could be help you with your desired output.
Have you tried using a UIScrollView as the background ? i.e the area where you do not want touch events to be fired.
UIScrollView does not call the touch methods.
You can add UIImageView Control on that area where you want to disable touch event.you can add UIImageView object as top of self.view subViews.
Example
//area -- is that area where you want to disable touch on self.view
UIImageView *imageView=[[UIImageView alloc]initWithFrame:area];
[self.view addSubView:imageView];
You touchDelegate will always call in this way, but if you are doing some task on touch then you can do your task like this way.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
UIButton *touchObject=(UIButton*)[touch view];
if ([touchObject isKindOfClass:[UIButton class]])
{
//Do what ever you want on button touch
}
else{
return;
}
}
I have a MapView, Over which i have added a UIView (0, 216, 320, 200) contain 10 UIButtons. This UIView is to work like a menu, and Shows and Hides on a certain button action.
The problem here is, While the Menu is showing, when i tap a button single ime, it works perfectly, but tapping it very quickly multiple times makes the MapView Zoom(Which is under the UIView which contain the button). It means the touch event is passing to MapView.
Detail: Tapping on the button very quickly cause in MapView Zoom, and the Button Action performs when you stop tapping.
How can i prevent that?
What you could do is disable user interaction for the duration of your button's handler. So in the selector do:
-(void)buttonSelector:(id)sender {
_mapView.userInteractionEnabled = NO;
// ... do what your button needs to do
_mapView.view.userInteractionEnabled = YES;
}
Of course, this assumes your button gets the event before the map view... which I think it should because it's on top of the map view.
To handle the rapid tapping, you could create a subclass of UIButton and use the following code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
_mapView.userInteractionEnabled = NO;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
_mapView.userInteractionEnabled = YES;
}
In the builder change the class of the UIButton to your subclass. If you debug the touch in the set passed to the touchesEnd method and look at the tapCount, you'll see all your taps have been caught here.
Yes, so the problem was, I was adding UIView as subview over MapView.. I fixed it by adding the UIView as subview on self.view
I have several UIImageView as subview of a UIView that act as a canvas. The ImageViews receive touch events, so I can move them.
If I pan two views, each finger on each of them I can move two views at the same time. I don't want that.
I checked the maximumNumberOfTouches property on the superview but it affects that view, but it doesn't prevent that each other subview receives the touch event.
Any ideas how to avoid this behavior?
Thanks
How about just setting a flag that an image view is already moving, and the pan gesture on all views check that flag before actually moving their views? Just set the flag to false once the view has stopped moving.
you may do like this , extend UIImageView as a Custom UIView may called YouImageView
in the touch event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for ( UIView *v in self.superview.subviews )
{
if ( v != self )
{
v.userInteractionEnabled = NO;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for ( UIView *v in self.superview.subviews )
{
v.userInteractionEnabled = YES;
}
}
done!
Basically, here is my view hierarchy (and I appologize if this is hard to read... I'm new here so posting suggestions happily accepted)
--AppControls.xib
-------(UIView)ControlsView
----------------- (UIView)TopBar
----------------- -------------- btn1, btn2, btn3
----------------- UIView)BottomBar
----------------- --------------slider1 btn1, btn2
--PageContent.xib
----------------- (UIView)ContentView
----------------- --------------btn1, btn2, btn3
----------------- --------------(UIImageView)FullPageImage
My situation is that I want to hide and show the controls when tapping anywhere on the PageContent thats not a button and have the controls show, much like the iPhone Video Player. However, when the controls are shown I still want to be able to click the buttons on the PageContent.
I have all of this working, except for the last bit. When the controls are showing the background of the controls receives the touch events instead of the view below. And turning off user interaction on the ControlsView turns it off on all its children.
I have tried overriding HitTest on my ControlsView subclass as follows which I found in a similar post:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *hitView = nil;
NSArray *subviews = [self subviews];
int subviewCount = [subviews count];
for (int subviewIndex = 0; !hitView && subviewIndex < subviewCount; subviewIndex++){
hitView = [[subviews objectAtIndex:subviewIndex] hitTest:point withEvent:event];
}
return hitView;
}
However, at this point my slider doesn't work, nor do most of the other buttons, and really, things just start getting weird.
So my question is in short: How do I let all the subviews of a view have touch events, while the super view's background is unclickable, and the buttons on views below can receive touch events.
Thanks!
You're close. Don't override -hitTest:withEvent:. By the time that is called, the event dispatcher has already decided that your subtree of the hierarchy owns the event and won't look elsewhere. Instead, override -pointInside:withEvent:, which is called earlier in the event processing pipeline. It's how the system asks "hey view, does ANYONE in your hierarchy respond to an event at this point?". If you say NO, event processing continues below you in the visible stack.
Per the documentation, the default implementation just checks whether the point is in the bounds of the view at all.
Your strategy is to say "yes" when any of your subviews is at that coordinate, but say "no" when the touch would be hitting the background.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
for (UIView * view in [self subviews]) {
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
return NO;
}
Thanks to #Ben Zutto, Swift 3 solution:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for view in self.subviews {
if view.isUserInteractionEnabled, view.point(inside: self.convert(point, to: view), with: event) {
return true
}
}
return false
}
Another approach may be to have an invisible full-screen button behind everything else, and take appropriate action when it is hit.
A slight variant on Ben's answer, dealing w/ children which extend outside their parent.
If clipChildren is YES, then this will not return YES for points which are outside the main control but inside some child.
if clipChildren is NO, this is the same as Ben's.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL clipChildren = YES;
if (!clipChildren || [super pointInside:point withEvent:event]) {
for (UIView * view in [self subviews]) {
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
}
return NO;
}
I have a UIView with multiple text boxes. Now i have used delegates to change the responder from from text field to another. In this case my key board goes away when the user comes to last text field.
but now i want to hide my key board when the user touches UIView(touches any where on screen apart from text boxes). Can some one help me with this.
Thanks
Use resignFirstResponder in touchesBegan, like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
if(touch.phase == UITouchPhaseBegan) {
[self.myTextField resignFirstResponder];
}
}
You may need to call this on multiple text fields if you're not sure where the focus is currently located; I haven't tested that case.
Additionally, in Interface Builder, you'll need to enable the "User Interaction Enabled" checkbox for the view, or programatically use:
myView.userInteractionEnabled = YES;
Just call this in your view controller when you want to hide the keyboard.
[self.view endEditing:NO];
I use
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if(touch.phase==UITouchPhaseBegan){
//find first response view
for (UIView *view in [self.view subviews]) {
if ([view isFirstResponder]) {
[view resignFirstResponder];
break;
}
}
}
}
According to Beginning iPhone Development, you draw a round rect button so that it covers your entire UI; the complete screen. Then go to the Layout menu and click Send to Back. Then in the inspector, change the button's type from round rect to Custom. Now add a touch up inside event to this button and attach it to a method which handles it. Within the body of this method, make sure you have the statements:
[myTextFieldOne resignFirstResponder];
[myTextFieldTwo resignFirstResponder];
Basically send the resignFirstResponder message to each of your text fields, or any field that can produce a keyboard.
I'm actually really new to the iPhone SDK. I don't know if this is the best method, but it works and it's what I learned from the aforementioned book. Hope it helps.
A super easy way to do this is working on what PCheese said.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
if(touch.phase == UITouchPhaseBegan) {
[self.view endEditing:YES];
}
}
I previously used [self.view endEditing:YES]; for a button event, but when combined with the previous answer it works a treat. Much easier, and you don't have to do anything else - just put it in your .m file. Also, this works with all (at least, as far as I have seen) text fields (works for UITextField and UITextView).
I know OP probably won't still need this but I just want to share it for others with the same problem.
Thanks Blaenk, I was trying to work out how to do this and didn't realise I could put a button in the background, nice trick! Here's my contribution (new to this site and to Cocoa Touch so it may not be the most robust code ever, but it's working so far...):
I call this method from the touchUpInside event on my UIButton:
-(void)closeKeyboard:(id)sender {
UIView *theFirstResponder = [self findFirstResponder];
if (theFirstResponder) {
[theFirstResponder resignFirstResponder];
}
}
Where this loop finds the firstResponder:
- (UIView *)findFirstResponder {
UIView *firstResponderView = nil;
for (UIView *view in [self entryFields]) {
if ([view isFirstResponder]) {
firstResponderView = view;
break;
}
}
return firstResponderView;
}
It is dependent on each of the UITextField controls in the view having a tag assigned to them (again, can do that in Interface Builder).
My two cents anyway, though i'd better give something back!
SO I agonized over this and I figured it out..
you don't need the other button or anything.
All you need to do is select "Files owner", in the inspector drag from its textField outlet (or whatever you named it) to your actual textfield (via whatever you named it) This will be in addition to whatever inputs you already had wired up.
and ofcourse in addition to the Control outlet (UIview changed to UIControl via inspector) to files owner via backgroundtouched...the first thing we all tried
it work for me
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:NO];
}
only need into self.view have an UItextField
http://objdev.com/2013/11/Dismissing-iOS-Keyboard-Self-View-EndEditing
The most appropriate way of solving this problem is using the following method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if(![touch.view isMemberOfClass:[UITextField class]]) {
[touch.view endEditing:YES];
}
}
Note: This does not work on UIScrollView instances.
Change the class UIView to class UIControl from the identify tab of inspector.
Add this:
- (IBAction)tabBackground:(id) sender;
to your .h file.
Add this to your .m file:
- (IBAction)tabBackgroup:(id) sender {
[nameField resignFirstRespnder];
[numberField resignFirstResponder];
}
Connect your tabBackground from the inspector, action received section to the UIView (which is an an UIControl) and you're good to go.
For swift 4.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if touch?.phase == UITouchPhase.began {
touch?.view?.endEditing(true)
}
}