Add a tap gesture to a part of a UILabel - ios

I have a NSAttributedString like so:
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:#"testing it out #clickhere"];
NSInteger length = str.length;
[str addAttribute:NSForegroundColorAttributeName value:[UIColor bestTextColor] range:NSMakeRange(0,length)];
The NSMutableAttributedString gets set to a UILabel like so:
label.attributedText = str;
How do I make a tap gesture (or something clickable) to another viewcontroller for the words '#clickhere in the string above?
Thanks!

I think, the best way is adding the UIGestureRecognizer to your UILabel and validate the frame that you would like.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[_yourLabel addGestureRecognizer:singleTap];
- (void)handleTap:(UITapGestureRecognizer *)tapRecognizer
{
CGPoint touchPoint = [tapRecognizer locationInView: _yourLabel];
//Modify the validFrame that you would like to enable the touch
//or get the frame from _yourLabel using the NSMutableAttributedString, if possible
CGRect validFrame = CGRectMake(0, 0, 300, 44);
if(YES == CGRectContainsPoint(validFrame, touchPoint)
{
//Handle here.
}
}

Simply first add a gesture to your label
[label setUserInteractionEnabled:YES];
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[label addGestureRecognizer:gesture];
control your gesture area in the below method
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
static CGRect touchableRect = CGRectMake(100.0f, 0.0f, 100.0f, 50.0f); // Give your rect as you need.
CGPoint touchPoint = [gestureRecognizer locationInView:self.view];
if (CGRectContainsPoint(touchableRect, touchPoint))
{
//User has tap on where you want. Do your other stuff here
}
}

UITapGestureRecognizer *Tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapDetected)];
Tap.numberOfTapsRequired = 1;
// label Name Is your Label Name
[labelName addGestureRecognizer:Tap];
-(void)tapDetected
{
//your code
}

I'd just like to add on Ramshad's answer, about how to deal with the valid frame.
For this you might want to consider using UITextView instead of UILabel, which doesn't give you access to how it manages layout the text. By disabling editing, selection and scrolling, UITextView behaves roughly the same as a UILabel, except some padding you have to remove.
For convenience, you might want to add a little category to UITextView, in which you write a method to test if a point touches any of the characters in range.
- (BOOL)point:(CGPoint)point touchesSomeCharacterInRange:(NSRange)range
{
NSRange glyphRange = [self.layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL];
BOOL touches = NO;
for (NSUInteger index = glyphRange.location; index < glyphRange.location + glyphRange.length; index++) {
CGRect rectForGlyphInContainer = [self.layoutManager boundingRectForGlyphRange:NSMakeRange(index, 1) inTextContainer:self.textContainer];
CGRect rectForGlyphInTextView = CGRectOffset(rectForGlyphInContainer, self.textContainerInset.left, self.textContainerInset.top);
if (CGRectContainsPoint(rectForGlyphInTextView, point)) {
touches = YES;
break;
}
}
return touches;
}
This would also work for a fragment of text containing multiple words spreading across multiple lines due to word wrap. It would also work on localized texts as we deal with glyphs that are printed.

The idea is the same as the accepted answer. Here is the way in Swift.
Suppose you have set up your label ready.
youLabel.text = "please tab me!"
Add tapGestuerRecognizer to your label
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction))
yourLabel.addGestureRecognizer(tap)
Add the String extension method to String to calculate the string rect.
extension String {
func rect(font: UIFont) -> CGRect {
let label = UILabel(frame: .zero)
label.font = font
label.text = self
label.sizeToFit()
return label.frame
}
}
Calculate the available tap rect of the tap gesture.
let pleaseStringRect = "please".rect(font: yourFont)
let tapStringRect = "tap".rect(font: yourFont)
let tapAreaRect = CGRect(x: pleaseStringRect.width, y: tapStringRect.origin.y, width: tapStringRect.width, height: tapStringRect.height"
In the action do what you want
#objc private func tapAction(tap: UITapGestureRecognizer) {
let position = tap.location(in: content)
guard reporterFirstNameRect.contains(position) else {
return
}
// Do the tap action stuff
}
That's it, happy coding.

Related

Tap Gesture Not Firing

I am using the following code to attach gesture recognizers to a custom view. But for some reason I am not able to get the gestures to work.
for(NSString *option in _options) {
UIView *blind = [self createBlind:option];
blind.userInteractionEnabled = YES;
blind.tag = index;
[self addSubview:blind];
[self addGravityBehaviorToBlind:blind];
[self addCollisionBehaviorToBlind:blind];
[self addElasticityBehaviorToBlind:blind];
[self registerGestureRecognizer:blind];
_spacingY += 44;
index++;
}
}
-(void) registerGestureRecognizer:(UIView *) blind {
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(blindTapped:)];
[blind addGestureRecognizer:tapGestureRecognizer];
}
-(void) blindTapped:(UITapGestureRecognizer *) recognizer { <-- THIS NEVER GETS CALLED
NSLog(#"blindTapped");
}
For some reason the blindTapped method never gets called even if I tap on the blind view.
UPDATE:
Yes, I see the views on the screen. The views (blind) is a subview in another view. The parent view gets added to the controller.view. There is no place where I have disabled interaction.
Here is the createBlind method:
-(UIView *) createBlind:(NSString *) option {
UIView *blind = [[UIView alloc] initWithFrame:CGRectMake(0, _spacingY, 200, 44)];
blind.layer.borderWidth = 1.0f;
blind.layer.borderColor = [UIColor whiteColor].CGColor;
UILabel *optionTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, blind.bounds.size.width, blind.bounds.size.height)];
optionTitleLabel.text = option;
optionTitleLabel.font = [UIFont fontWithName:#"GillSans" size:17];
optionTitleLabel.textColor = [UIColor whiteColor];
optionTitleLabel.textAlignment = NSTextAlignmentCenter;
[blind addSubview:optionTitleLabel];
return blind;
}
I have removed all the gravity and UIKit methods but still I am not able to trigger the tap gesture.
SOLUTION:
When adding the custom view to my controller.view I was not setting the frame. Once the frame was setup everything worked.

Trying to switch specific UILabel text in list

I have a list of data coming from a database and one of the columns is a date field. Inside that date field I have the date and time. To save room, I have a tap feature to switch between the date and time each time the user touches that uilabel. My issue is trying to know which uilabel the user touched. I have each uilabel with it's own tag incrementing by 1, but I'm confused on how to tell my function which label got pressed.
This is inside a for loop:
_short_time = [date substringFromIndex:range.location + 2];
_short_date = [date substringWithRange:NSMakeRange(0, range.location)];
_dateSwitch = [[UILabel alloc] initWithFrame:CGRectMake(7, 9, 75, 20)];
_dateSwitch.textColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:1];
_dateSwitch.text = _short_date;
_dateSwitch.tag = i + 1;
_dateSwitch.textAlignment = NSTextAlignmentCenter;
[_dateSwitch setFont:[UIFont systemFontOfSize:13]];
UITapGestureRecognizer *gSwap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(switchDateTime:)];
This is the function being called "switchDateTime":
- (void) switchDateTime: (UITapGestureRecognizer *) sender
{
if (state == 1) {
if (_dateSwitch.tag == _fId) {
[_dateSwitch setText:_short_date];
state = FALSE;
}
} else {
[_dateSwitch setText:_short_time];
state = TRUE;
}
}
You have to add your gesture recognizer on your label like this:
[_dateSwitch addGestureRecognizer:gSwap];
Then on your method to identify which view the gesture recognizer is attached to:
UILabel *tappedLabel = (UILabel *)sender.view;

How can I detect the selectedSegmentIndex when long-pressing down?

UILongPressGestureRecognizer got added to UISegmentedControl.
Is there a way to detect selectedSegmentIndex when long-pressing down?
thanks, in advance.
Did you try adding a UILongPressGestureRecognizer to it? In viewDidLoad:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
longPress.delegate = self;
[segmentedControl addGestureRecognizer:longPress];
Don't forget to add UIGestureRecognizerDelegate to your header file.
To know where is pressed:
- (void)longPress:(UILongPressGestureRecognizer *)gestureRecognizer {
CGPoint p = [gestureRecognizer locationInView:segmentedControl];
}
Then you could check what segment of segmentedControl matches with CGPoint p, check for the Y-coordinate, for example. When it's left from the middle line of the UISegmentedControl it's segment 0, when it's right of that line it's segment 1.
You register for long press in UISegmentedControl
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(ListQuizViewController.segmentLongPress(_:)))
//longPress.delegate = self;
self.segmentedControl.addGestureRecognizer(longPress)
longPress.minimumPressDuration = 1
You get index of selected button as follows with the assumption that segments are equally spaced
func segmentLongPress(gestureRecognizer:UILongPressGestureRecognizer)
{
let p = gestureRecognizer.locationInView(self.segmentedControl)
let index = Int(ceil( p.x/(self.segmentedControl.frame.width/4))) - 1
self.segmentedControl.selectedSegmentIndex = index
}

Touch detection on UILabel inside UIScrollView

I have a UIScrollView and inside this I have UILabels. I need to detect touch events for the UILabels. At the moment, it is detecting the touch inside the second label only. It ignores the first.
I have the code -
Creating the UIScrollView
backGroundView = [[UIScrollView alloc] init];
backGroundView.frame= self.view.frame;
backGroundView.userInteractionEnabled = YES;
[backGroundView setScrollEnabled:YES];
backGroundView.showsVerticalScrollIndicator = YES;
backGroundView.contentSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
backGroundView.delegate = self;
[self.view addSubview:backGroundView];
Creating the UILabel
UILabel *OneDay = [[UILabel alloc] initWithFrame:CGRectMake(15, stockChart.bounds.origin.y + stockChart.bounds.size.height + 35, 40, 30)];
OneDay.text = #"1d";
OneDay.tag = 1;
OneDay.userInteractionEnabled = YES;
OneDay.layer.borderColor = [UIColor grayColor].CGColor;
OneDay.layer.borderWidth = 1.0f;
OneDay.textAlignment = UITextAlignmentCenter;
[OneDay addGestureRecognizer:detectTimeFrameChange];
[backGroundView addSubview:OneDay];
UILabel *FiveDay = [[UILabel alloc] initWithFrame:CGRectMake(45, stockChart.bounds.origin.y + stockChart.bounds.size.height + 35, 40, 30)];
FiveDay.text = #"5d";
FiveDay.tag = 2;
FiveDay.userInteractionEnabled = YES;
FiveDay.layer.borderColor = [UIColor grayColor].CGColor;
FiveDay.layer.borderWidth = 1.0f;
FiveDay.textAlignment = UITextAlignmentCenter;
[FiveDay addGestureRecognizer:detectTimeFrameChange];
[backGroundView addSubview:FiveDay];
Creating the gesturerecognizer
UITapGestureRecognizer *detectTimeFrameChange = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(timeFrameLabelTapped:)];
detectTimeFrameChange.numberOfTapsRequired = 1;
[backGroundView addGestureRecognizer:detectTimeFrameChange];
Handling gesture
-(void)timeFrameLabelTapped:(UITapGestureRecognizer*)recognizer{
if (recognizer.view.tag == 1) {
NSLog(#"One pressed");
}
else if (recognizer.view.tag == 2){
NSLog(#"2 pressed");
}
}
You can use this :
UITapGestureRecognizer *labelTap=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(labelTapped)];
labelTap.numberOfTapsRequired=1;
[yourLabel addGestureRecognizer:labelTap];
handle the touch tap event inside labelTapped method:
-(void)labelTapped
{
//your code to handle tap
}
Touch events are not detected on UIScrollview for getting your requirement add tap gestures to your label.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureCaptured)];
[OneDay addGestureRecognizer:singleTap];
-(void)singleTapGestureCaptured{
NSLog(#"touch detected");
}
You can find using tapgesturerecogniser like that...
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
[self.scrollview addGestureRecognizer:singleFingerTap];
//The event handling method
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
if(recognizer.view.tag == 1){}
//Do stuff here...
}
Where have you written the touchesBegan?
If you want to detect the touches in the label you'll have to create a subclass of label and write your touchesBegan there to detect the touch events
The problem here is that you are trying to use the same gesture recognizer for multiple views. A gesture recognizer can only be attached to a single view at once. You are only receiving events from the last view, because that is the view the recognizer is currently attached to. To fix the issue, simply create a gesture recognizer for each view you want to detect touches in.

Changing the text of a uilabel then add new uilabel

I want to change the text of a label, then have the user move it to where they want it on the screen (which is currently working) (the user hits - "Add text").
Once they place it where they would like. I want the "Add text" button to create a new label that the user can move. I'm not sure how to create these on the fly an to make sure that the gesture recognizers function with the new label. Thanks for suggestions.
This is what I have now,,, doesn't work quite yet.
-(IBAction)addText:(id)sender
{
textView.hidden=YES;
labelShirt.text= textField.text;
[textField resignFirstResponder];
[self addTextButtonPressed];
}
-(void)addTextButtonPressed
{
// CGRect *textFrame =
// myInitialFrame is a CGRect you choose to place your label
UILabel *myNewLabel = [[UILabel alloc] initWithFrame:CGRectMake(50,50,100,100)];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(labelMoved:)];
myNewLabel.text =textField.text;
[self.view addSubview:myNewLabel];
}
-(void)labelMoved:(UIPanGestureRecognizer *)sender
{
CGPoint translation = [sender translationInView:self.view];
sender.view.frame = CGRectOffset(sender.view.frame, translation.x, translation.y);
}
// The action that is added to your add text button
-(void)addTextButtonPressed
{
// myInitialFrame is a CGRect you choose to place your label
UILabel *myNewLabel = [[UILabel alloc] initWithFrame:myInitialFrame];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(labelMoved:)];
myNewLabel.text = #"My initial text";
// EDIT
[self.view addSubview:myNewLabel];
[myNewLabel addGestureRecognizer:panGestureRecognizer];
}
-(void)labelMoved:(UIPanGestureRecognizer *)sender
{
CGPoint translation = [sender translationInView:self.view];
sender.view.frame = CGRectOffset(sender.view.frame, translation.x, translation.y);
}
I don't know if that's enough to solve your problem, just comment if you still need more explanation.

Resources