I read that UILabels aren't meant to respond to touch events, and that I could just use a UIButton. However, I have to subclass UILabel anyways to override another method, so I thought I might as well use a label to keep changes to my code a minimum.
How can I get my label to respond to touch events? The code and error displayed are below.
UILabel *tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(startingPoint, 5, 10, 22)];
tempLabel.text = equationText;
tempLabel.font = [UIFont systemFontOfSize:13];
[tempLabel sizeToFit];
[view addSubview:tempLabel];
[tempLabel addTarget:self action:#selector(updateLabel:) forControlEvents:UIControlEventTouchUpInside]; // UNRECOGNIZED SELECTOR SENT TO INSTANCE
Since UILabel isn't a control, you can't send the -addTarget:action:forControlEvents: message. You must remove that line from your application since your label is not a control and will never respond to that message. Instead, if you wanted to use your label, you could set it interactive and add a gesture recognizer to it:
// label setup code omitted
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(updateLabel:)];
[tempLabel setUserInteractionEnabled:YES];
[tempLabel addGestureRecognizer:tap];
[tap release]; // if not using ARC
The callback for the gesture recognizer will be passed the instance of the gesture recognizer that triggered it, not the control like an action message would. To get the instance of the label that triggered the event, message the passed-in gesture recognizer with -view. So, if your updateLabel: method might be implemented as below:
- (void)updateLabel:(UIGestureRecognizer*)recognizer
{
// Only respond if we're in the ended state (similar to touchupinside)
if( [recognizer state] == UIGestureRecognizerStateEnded ) {
// the label that was tapped
UILabel* label = (UILabel*)[recognizer view];
// do things with your label
}
}
Also, the gesture recognizer will call the action method with multiple states, similar to those found in the -touchesBegan:... series of methods. You should check that you're only committing work while the recognizer is in the appropriate state. For your simple tap gesture recognizer, you probably only want to do work when the recognizer is in the UIGestureRecognizerStateEnded state (see the example above). For more information on gesture recognizers, see the documentation for UIGestureRecognizer.
//create label
_label = [[UILabel alloc] initWithFrame:CGRectMake(self.view.center.x-75,self.view.frame.size.height-60, 150, 50)];
_label.backgroundColor = [UIColor clearColor];
_label.textColor=[UIColor whiteColor];
_label.text = #"Forgot password ?";
UITapGestureRecognizer *recongniser = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapAction)];//ADD ACTION TO LABEL
[_label setUserInteractionEnabled:YES];
[_label addGestureRecognizer:recongniser];
//NAVIGATE TO ONEVIEW TO ANOTHER VIEW
-(void) tapAction //METHOD TO ADD IT TO LABEL SELECTOR
{
_forgotviewController=[[ForgotPassword alloc]init];
[self.navigationController pushViewController:self.forgotviewController animated:YES];
}
Smartest thing to do here is to use a UIButton to do what you are trying to do.
But if you really want to subclass UILabel, make sure to set userInteractionEnabled to YES.
The documentation says:
New label objects are configured to disregard user events by default.
If you want to handle events in a custom subclass of UILabel, you must
explicitly change the value of the userInteractionEnabled property to
YES after initializing the object.
And addTarget: action: forControlEvents: wouldn't work, because UILabel isn't descended from UIControl. One place you can catch your events by implementing UIResponder's touchesBegan:withEvent: method in your subclass.
Here is swift 2.1 version for UILabel tap
let label = UILabel(frameSize)
let gesture = UITapGestureRecognizer(target: self, action: "labelTapped:")
labelHaveAccount.userInteractionEnabled = true
labelHaveAccount.addGestureRecognizer(gesture)
func labelTapped(gesture:UIGestureRecognizer!){
//lable tapped
}
Related
I have a UIView that contains a UITextView. The UIView is initiated inside a UIViewController.
But when I touch the UITextView box, nothing happens. Neither the keyboard appears nor delegate methods respond to interaction.
Code:
noteText = [[UITextView alloc]initWithFrame:CGRectMake(0, 0, 700, 240)];
noteText.layer.borderColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2].CGColor;
noteText.layer.borderWidth = 2;
[noteText setEditable:YES];
noteText.userInteractionEnabled = YES;
noteText.returnKeyType = UIReturnKeyDone;
noteText.font = [UIFont fontWithName:#"Calibri" size:16];
noteText.textColor = [UIColor blackColor];
[self addSubview:noteText];
Update 1
I removed all other views from the UIViewController, and only put a UITextView in the UIViewController and still not reacting. Neither cursor or keyboard appear.
noteText = [[UITextView alloc]initWithFrame:CGRectMake(0, 0, 700, 240)];
noteText.layer.borderColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2].CGColor;
noteText.layer.borderWidth = 2;
[noteText setEditable:YES];
noteText.userInteractionEnabled = YES;
noteText.returnKeyType = UIReturnKeyDone;
noteText.font = [UIFont fontWithName:#"Calibri" size:16];
noteText.textColor = [UIColor blackColor];
[self addSubview:noteText];
// Two suggestions
self.userInteractionEnabled = YES; // superview may blocks touches
self.clipsToBounds = YES; // superview may clips your textfield, you will see it
I found the error.
In one class I categorize the UITextView instead of subclass and set canBecomeFirstResponder to NO.
try this:
in your .h file conform the delegate UITextViewDelegate
:<UITextViewDelegate>
in your .m file:
noteText.delegate = self;
And delegate methods:
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
[textView becomeFirstResponder];
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
//resign for exapmple
return YES;
}
Hope this help!
I know it's late, but maybe for someone it'll be helpful. I had the same problem, and it disappeared after I used
[self setSelectable:true];
in my UITextView subclass.
Check this things:
add your viewcontroller on the right UIWindow.
set window makeKeyAndVisible and add rootViewController to window at this method:
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
I think here is something wrong in xib file or at appdelegate.
Try overriding - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { inside the UIView to see if it's receiving touches
Just in case, try setting userInteractionEnabled = YES; for your UIView object. If it's somehow set as NO it will trickle down to all of its subviews
Maybe you have some mistake in your overriden -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event method? Maybe you do resignFirstResponder there or put some view above all?
Whenever something like this happens check the following items,
Make sure that the Text view is properly retained and released only in dealloc.(If you are not using ARC)
Check the frame of both Text view and it's parent views. Make sure that the subview is fitting exactly inside the parent view. Also make sure that this is the case with all the superviews of text view.
Check whether the delegate is set properly.
If all these are done, try adding a button on top of text view and check if it's target selector is getting called. If yes, the issue is with text view delegate or release statement. Otherwise the issue is with frame setting of Text view or it's superviews.
I want to add some static text to a UITableViewCell in a UITextView.
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setBackgroundColor:[UIColor clearColor]];
[addressField setFont:[UIFont fontWithName:#"HelveticaNeue" size:14]];
[addressField setContentInset:UIEdgeInsetsMake(0, 20, 0, 0)];
[addressField setEditable:NO];
[addressField setScrollEnabled:NO];
// change me later
[addressField setText:#"John Doe\n555 Some Street\nSan Francisco, CA, 00000"];
[cell.contentView addSubview:addressField];
[addressField release];
This works great but I this code makes the cell unselectable probably because the UITextView is covering the entire cell.
How can I work around this so that I can have both the UITextView and selectable cells?
btw, I could make the UITextView size a bit smaller but users would still not be able to select the cell if they touch the UITextView.
I think a slightly better way to do it is to create a tap gesture recognizer on the entire table. (For example in your viewDidLoad)
// gesture recognizer to make the entire cell a touch target
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(changeFocus:)];
[tableView addGestureRecognizer:tap];
[tap release];
Then you create a selector (changeFocus: in this case) to do the actual selecting.
- (void)changeFocus:(UITapGestureRecognizer *)tap
{
if (tap.state == UIGestureRecognizerStateEnded)
{
CGPoint tapLocation = [tap locationInView:self.tableView];
NSIndexPath* path = [self.tableView indexPathForRowAtPoint:tapLocation];
[self tableView:self.tableView didSelectRowAtIndexPath:path];
}
}
You can make your changeFocus method more elaborate to prevent selections or give focus to specific subviews of the selected indexPath.
I would adopt the following approach in order to keep interaction enabled with both the UITextView and the UITableViewCell.
Declare your controller class (a UITableViewController I guess ?) as UITexView delegate.
When you declare your UITextView, set the table view controller as it's delegate.
Implement one of the UITextViewDelegate methods (ex : - (void)textViewDidChangeSelection:(UITextView *)textView) in your table view controller .m file.
From within this method you can manipulate the targeted cell either with a custom code or by triggering the tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *) delegate method through selectRowAtIndexPath:animated:scrollPosition:.
Your code might then look like :
In the table view controller .h file :
#interface MyTableViewController : UITableViewController <UITextViewDelegate> { ...
...
}
In the table view controller .m file :
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setDelegate:self];
...
Then implement this function for example (or any other suitable UITextViewDelegate function) :
- (void)textViewDidChangeSelection:(UITextView *)textView {
// Determine which text view triggered this method in order to target the right cell
...
// You should have obtained an indexPath here
...
// Call the following function to trigger the row selection table view delegate method
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]
}
Note that there are other alternatives like subclassing UITextView and deal with it's touch methods. I would recommend to use the possibilites offered by its delegate protocol though.
Note also that it might be handy to have your UITextView declared or at least referenced as an instance variable of the table view controller class. This will help you easily keep track of which addressField was hit and get the right indexPath.
[addressField setUserInteractionEnabled:NO];
I hope this helps you a bit:
[self.view insertSubview:TextView aboveSubview:TableView];
Or vice-versa based on your requirements.
I've got a UIButton in my application called myButton. I'd like to react to a single finger tap and a two finger tap on the button differently. However, from what I understand UIButton objects are only able to detect single finger touches touchDown, touchUpInside, etc. After doing some research, it looks like I'll have to use the touchesBegan method and just check to see if both of the fingers are within myButton frame. Is there any easier way to do this? Thanks!
Yes it is! By using UITapGestureRecognizer you can do something like this
- (void)viewDidLoad {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)]
tap.numberOfTapsRequired = 2;
[myButton addGestureRecognizer:tap];
[tap release];
}
- (void)tap:(UITapGestureRecognizer *)gesture {
NSLog(#"Tap!");
}
When I add a UIImageView subclass (fullscreen) behind a couple of UIButton subclass instances, those buttons stop receiving the touches and don't function anymore. Nothing I have tried has worked to restore their connectivity to user touches.
I have tried:
a) Using a UIImageView subclass for the front view (that receives the touches) and making sure setUserInteractionEnabled is YES. Result: UIButtons are not responding when the UIView is present.
b) Explicitly passing the touches from the front view to the view controller in hopes that it will do the right thing. Result: UIButtons are not responding to the touches.
c) Calling the touchesBegan, touchesMoved and touchesEnded methods directly on the button I want to interact with when any touch on the front UIView is received. Result: Strikingly, the buttons don't respond even when I am cramming the touch events down their throat.
d) Passing the touches from the front view to the UIViewController, and iterating over all subviews handing out the touches manually. I included a touchesBegan method showing how I tried to do this. When executed the loop is infinite, it never progresses past the first view (is of type FrameUIView).
I know this problem is likely the result of my lack of understanding of proper view hierarchy or the responder chain.
if I comment the last view created after the button, then the buttons work fine. Is there something special about UIButtons in this respect?
Here is the code.
I have a UIViewController subclass, in it:
- (void)loadView {
mainAppView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = mainAppView;
frameImageView = [[FrameUIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
frameImageView.image = [UIImage imageNamed:[[[ThemeController sharedInstance] currentTheme] frameImageFilename]];
[frameImageView setUserInteractionEnabled:YES];
[mainAppView addSubview:frameImageView];
zoomInButton = [[CustomUIButton alloc] initWithFrame:CGRectMake(controlsOriginX + zoomWidth + lightWidth, controlsOriginY, zoomWidth, controlsHeight)];
[zoomInButton setTitle:#"+" forState:UIControlStateNormal];
[[zoomInButton titleLabel] setFont:[UIFont systemFontOfSize:28]];
[zoomInButton addTarget:self action:#selector(doMyAction) forControlEvents:UIControlEventTouchDown];
[zoomInButton addTarget:self action:#selector(cancelMyAction) forControlEvents:UIControlEventTouchUpInside];
[mainAppView addSubview:zoomInButton];
FrameFrontUIView *frameFrontImageView = [[FrameFrontUIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
frameFrontImageView.image = [UIImage imageNamed:[[[ThemeController sharedInstance] currentTheme] frameFrontImageFilename]];
[frameFrontImageView setUserInteractionEnabled:YES];
[mainAppView addSubview:frameFrontImageView];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UIView *view in self.view.subviews) {
NSLog(#"view is a %#", [view description]);
[view touchesBegan:touches withEvent:event];
}
}
Anyone with a tip on how to do this wins the internets and a cookie. Also accepting revolvers for russian roulette mayhem. Thank you.
Have you tried using the buttons without the image view behind them? Subclassing UIButton is not advisable. The way to instantiate UIButtons is with factory methods:
UIButton* myButton = [UIButton buttonWithType:UIButtonTypeCustom];
So my guess is that the CustomUIButton instances you have created are not properly set up.
This question is old, but I just ran into the same problem.
The solution I used is to create a container UIView, then add the UIImageView as a subview, then add the buttons, text boxes, etc. over that. Seems to work well.
I don't really understand why this is an issue with using a UIImageView as the superview for buttons.
Is it ok to override -handlePan: in a UIScrollView subclass?
i.e. my app won't get rejected from the app store?
Thanks for sharing your views.
Edit: what about calling -handlePan: in another method of my subclass?
In case anyone is interested, what I did instead of overriding was disabling the default UIPanGestureRecognizer and adding another instance of UIPanGestureRecognizer which is mapped to my custom handler.
Edit for twerdster:
I did it like this
//disables the built-in pan gesture
for (UIGestureRecognizer *gesture in scrollView.gestureRecognizers){
if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]){
gesture.enabled = NO;
}
}
//add your own
UIPanGestureRecognizer *myPan = [[UIPanGestureRecognizer alloc] init...];
//customize myPan here
[scrollView addGestureRecognizer:myPan];
[myPan release];
You can make the code even shorter.
//disables the built-in pan gesture
scrollView.panGestureRecognizer.enabled = NO;
//add your own
UIPanGestureRecognizer *myPan = [[UIPanGestureRecognizer alloc] init...];
[scrollView addGestureRecognizer:myPan];
[myPan release];