Touches - User interaction of parent and child - iphone

I have an issue regarding Touches, maybe several people have had it though.
I have a view that has some Labels and imageviews on it. If i disabled the touches of my View by saying userinteraction disabled then all the touches of all subviews get disabled, what if i may want to have touches enabled for a few and disabled for a few, when userinteraction of view is disabled.
Is this the only solution: Create two seperate views, out of which one's user interaction would be enabled and others' would be disabled and implement my stuff on top of it?
Regards,
Reno Jones

Usually UILabel and UIImageView don't have touch event by default. So you only need to enable touch event where you want to have them.
i strongly recommmend to use always UIButton like this:
- (void)viewDidLoad {
UIButton *buttonActionA = [[UIButton alloc] initWithFrame:CGRectMake(50.0, 50.0, 100.0, 100.0)];
[buttonActionA addTarget:self action:#selector(doActionA) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonActionA];
[buttonActionA release];
}
- (void) doActionA {
NSLog(#"This is Action A");
}
and avoid subclassing UIView just to implement
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
did that answer your question ?

Related

Disable touch events on certain areas of iPhone screen

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;
}
}

Intercept TouchUpInside event in UITableView while tableview scrolls

I'm building out a UITableView that has a button in the tableview's section header, so that as the user scrolls down the screen, the button is pinned to the top of the screen.
The problem is that the touch events for the section header's button don't register when the UITableView is moving. The button only gets the touch event if the tableview is not scrolling.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if (!_sectionHeaderView) {
_sectionHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 200)];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
backButton.frame = CGRectMake(0, 0, 50, 50);
[backButton setImage:[UIImage imageNamed:#"upArrow"] forState:UIControlStateNormal];
[backButton addTarget:self action:#selector(scrollToTop:) forControlEvents:UIControlEventTouchUpInside];
[_sectionHeaderView addSubview:backButton];
}
return _sectionHeaderView;
}
Currently, what happens is:
- If the tableview is not scrolling, the button performs as you'd expect: tap it, and the button gets the TouchUpInside gesture.
- If the tableview is scrolling, the button doesn't get the TouchUpInside gesture - instead, the tableview just stops in place.
I've tried subclassing the UITableView and looking at the touchesBegan: methods, etc and that didn't work. I see the same pattern: the gestures only show up if/when the tableview isn't moving.
Update:
This isn't a usability issue - while I can't show screenshots of the design I'm working on, it's a valid use case to use buttons & controls in the section header.
Here's a quick sketch to explain why:
Figured it out! All you need to do is subclass UITableView, and override hitTest:withEvent:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ([[super hitTest:point withEvent:event] isKindOfClass:[UIButton class]]) {
UIButton *buttonThatWasTapped = (UIButton *)[super hitTest:point withEvent:event];
[buttonThatWasTapped sendActionsForControlEvents:UIControlEventTouchUpInside];
}
return [super hitTest:point withEvent:event];
}
Here's what this code does: if the tableview is touched, and the touch occurred in the bounds of a UIButton, the tableview will forward the touch event to the UIButton.
There's a caveat here: the touch event may get sent to the UIButton several times (I'm seeing the call being made 3 times for each touch). This code was sufficient for my needs, but if you're using this code in your app, watch out for that issue - you may have to modify the above code to account for this.
This is what's supposed to happen.
The user is used to this behavior - it allows them to stop the table view's scrolling when they see what they wanted to touch.
Imagine I was scrolling through a table looking for the row 'unicorns', and I was scrolling really fast (because it's a big table, and I'm not sure where unicorns is). When 'unicorns' crosses my vision, I will instinctively stop the table to select it.
With the current system, I can tap anywhere on the table view and it will stop. If table views were implemented your way, I would tap 'underground rockchucks' to stop the table, and all of a sudden I am looking at information about rockchucks - and I want information about Unicorns.
I now become a disgruntled user, return your app to Apple, and get my $0.99 back.
You don't want users doing that.

How to handle touch event on UILabel as subview of UITableViewCell?

My app has a custom UITableView. In the cellForRowAtIndexPath delegate method of its UIViewController I am instantiating custom UITableViewCell objects that contain multiple custom UILabels (actually a sub-class of OHAttributedLabel) as subviews of the content view.
I have tried setting userInteractionEnabled = YES on the label, then adding touch events in the view controller, but that isn't working.
Ideas?
Thanks
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if (CGRectContainsPoint([self.site frame], [touch locationInView:self.view])){
//do whatever you want
}
}
Or
UILabel *label = =[UILabel alloc]init];
label.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture =
[[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(labelTap)] autorelease];
[label addGestureRecognizer:tapGesture];
A UILabel isn't a UIControl so you won't get events on UIControlEventTouchUpInside or similar. Why not use a button instead? You can make it look exactly like a label.
Regardless you will probably need to set addTarget:action:forControlEvents: and tag on the UIButton in your cellForRowAtIndexPath: method. In that method, detect which cell's button was tapped by examining the tag value.
If you must use UILabel then you need to subclass it and intercept the touchesBegan/touchesEnded methods (inherited from UIResponder) to detect UIControlEventTouchUpInside yourself.
Problem in OHAttributedLabel. This label also handles tap on links. So for handle tap on any point of label (not just link) you must
self.textLabel.onlyCatchTouchesOnLinks = NO;
Where self.textLabel is your OHAttributedLabel.
And don't forget of userInteractionEnabled.
I don't know if it is the same problem but... I added a label and could not get it to recognize a touch, I eventually realised that it was because I was adding it as a subview, but its frame was outside its parent's frame, hence the touch heirarchy broke
I just had the problem with using static table cells for a settings table where I wanted the whole cell to trigger the first responder for the cell's textfield.
I ended up adding a transparent (custom, blank title) button behind the label (touch disabled) and textfield after not getting any touches using gesture recognizers. I think it should work in a more elegant way, but it solved the task for now and that limited purpose. (and you can just drag connect from the button's default action)
Bit ugly. Then again, it just describes the area behind the text field reacting to touch. Which was the intention after all. So maybe its just not that fancy.
Will keep it until I find the reason for the recognizers not firing.
you can use TTTAttributedLabel to instead it. it's very easy.
when you initial the UITableViewCell,you can delegate:TTTAttributedLabelDelegate
like :
#interface MyTableViewCell : UITableViewCell<TTTAttributedLabelDelegate>{
UILabel *nameLabel;
TTTAttributedLabel *valueLabel;
}
when you initial ,you could add link to label :
[valueLabel addLinkToPhoneNumber:valueStr withRange:NSMakeRange(0, valueStr.length)];
so,you could do anything you want:
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber{
//do anything you want.
}

How to drag and drop a UIButton

I have four UIButtons in my interface file. I have outlets attached to each button. I want to be able to drag and drop them across the screen. What is the best way to do this? How can I do this? please help!!!
try this u can drag and drop your button what ever you want
implement this code in ViewDidLoad
[self.shoeimageOutlet addTarget:self action:#selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[self.shoeimageOutlet addTarget:self action:#selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragOutside];
it will call the method written below
- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
UIControl *control = sender;
control.center = point;
}
There are 3 possibilities nowadays on how to enable items – that is UIViews and UIControls – to be draggable.
override touchesMoved
add a target/selector for dragging control events
add a pan gesture recognizer
The complete tutorial found here !!!

How to disable touch input to all views except the top-most view?

I have a view with multiple subviews. When a user taps a subview, the subview expands in size to cover most of the screen, but some of the other subviews are still visible underneath.
I want my app to ignore touches on the other subviews when one of the subviews is "expanded" like this. Is there a simple way to achieve this? I can write code to handle this, but I was hoping there's a simpler built-in way.
Hope this help...
[[yourSuperView subviews]
makeObjectsPerformSelector:#selector(setUserInteractionEnabled:)
withObject:[NSNumber numberWithBool:FALSE]];
which will disable userInteraction of a view's immediate subviews..Then give userInteraction to the only view you wanted
yourTouchableView.setUserInteraction = TRUE;
EDIT:
It seems in iOS disabling userInteraction on a parent view doesn't disable userInteraction on its childs.. So the code above (I mean the one with makeObjectsPerformSelector:)will only work to disable userInteraction of a parent's immediate subviews..
See user madewulf's answer which recursively get all subviews and disable user interaction of all of them. Or if you need to disable userInteraction of this view in many places in the project, You can categorize UIView to add that feature.. Something like this will do..
#interface UIView (UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value;
#end
#implementation UIView(UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value{
self.userInteractionEnabled = value;
for (UIView *view in [self subviews]) {
[view setRecursiveUserInteraction:value];
}
}
#end
Now you can call
[yourSuperView setRecursiveUserInteraction:NO];
Also user #lxt's suggestion of adding an invisible view on top of all view's is one other way of doing it..
There are a couple of ways of doing this. You could iterate through all your other subviews and set userInteractionEnabled = NO, but this is less than ideal if you have lots of other views (you would, after all, have to subsequently renable them all).
The way I do this is to create an invisible UIView that's the size of the entire screen that 'blocks' all the touches from going to the other views. Sometimes this is literally invisible, other times I may set it to black with an alpha value of 0.3 or so.
When you expand your main subview to fill the screen you can add this 'blocking' UIView behind it (using insertSubview: belowSubview:). When you minimize your expanded subview you can remove the invisible UIView from your hierarchy.
So not quite built-in, but I think the simplest approach. Not sure if that was what you were thinking of already, hopefully it was of some help.
Beware of the code given as solution here by Krishnabhadra:
[[yourSuperView subviews]makeObjectsPerformSelector:#selector(setUserInteractionEnabled:) withObject:[NSNumber numberWithBool:FALSE]];
This will not work in all cases because [yourSuperView subviews] only gives the direct subviews of the superview. To make it work, you will have to iterate recursively on all subviews:
-(void) disableRecursivelyAllSubviews:(UIView *) theView
{
theView.userInteractionEnabled = NO;
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
-(void) disableAllSubviewsOf:(UIView *) theView
{
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
Now a call to disableAllSubviewsOf will do what you wanted to do.
If you have a deep stack of views, the solution by lxt is probably better.
I would do this by putting a custom transparent button with the same frame as the superView. And then on top of that button I would put view that should accept user touches.
Button will swallow all touches and views behind it wouldn't receive any touch events, but view on top of the button will receive touches normally.
Something like this:
- (void)disableTouchesOnView:(UIView *)view {
UIButton *ghostButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)];
[ghostButton setBackgroundColor:[UIColor clearColor]];
ghostButton.tag = 42; // Any random number. Use #define to avoid putting numbers in code.
[view addSubview:ghostButton];
}
And a method for enabling the parentView.
- (void)enableTouchesOnView:(UIView *)view {
[[view viewWithTag:42] removeFromSuperview];
}
So, to disable all views in the parentViev behind yourView, I would do this:
YourView *yourView = [[YourView alloc] initWithCustomInitializer];
// It is important to disable touches on the parent view before adding the top most view.
[self disableTouchesOnView:parentView];
[parentView addSubview:yourView];
Just parentView.UserInteractionEnabled = NO will do the work.
Parent view will disable user interaction on all the view's subviews. But enable it does not enable all subviews(by default UIImageView is not interactable). So an easy way is find the parent view and use the code above, and there is no need to iterate all subviews to perform a selector.
Add a TapGestureRecognizer to your "background view" (the translucent one which "grays out" your normal interface) and set it to "Cancels Touches In View", without adding an action.
let captureTaps = UITapGestureRecognizer()
captureTaps.cancelsTouchesInView = true
dimmedOverlay?.addGestureRecognizer(captureTaps)
I will give my 2 cents to this problem.
Iteratively run userInteractionEnabled = false it's one way.
Another way will be add a UIView like following.
EZEventEater.h
#import <UIKit/UIKit.h>
#interface EZEventEater : UIView
#end
EZEventEater.m
#import "EZEventEater.h"
#implementation EZEventEater
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = false;
}
return self;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//EZDEBUG(#"eater touched");
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
In your code you add the EZEventEater view to cover all the views that your may block the touch event.
Whenever you want to block the touch event to those views, simply call
eater.userInteractionEnabled = YES;
Hope this helpful.
In Swift 5, I achieved this behaviour by placing a view right on top(the highlighted one) and setting:
myView.isUserInteractionEnabled = true
This does not let the touches go through it, thus ignoring the taps.
For my app, I think it will be sufficient to disable navigation to other tabs of the app (for a limited duration, while I'm doing some processing):
self.tabBarController.view.userInteractionEnabled = NO;
Also, I disabled the current view controller--
self.view.userInteractionEnabled = NO;
(And, by the way, the recursive solutions proposed here had odd effects in my app. The disable seems to work fine, but the re-enable has odd effects-- some of the UI was not renabled).
Simple solution. Add a dummy gesture that does nothing. Make it reusable by adding it to an extension like this:
extension UIView {
func addNullGesture() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(nullGesture))
addGestureRecognizer(gesture)
}
#objc private func nullGesture() {}
}
setUserInteractionEnabled = NO on the view you want to disable
I had the same problem, but the above solutions did not help.
I then noticed that calling
super.touchesBegan(...) was the problem.
After removing this the event was only handled by the top-most view.
I hope this is of help to anybody.