Hiren Prajapati
Hiren Prajapati

Reputation: 727

What is the best way to add UITapGestureRecognizer to a specific part of UILabel?

This question might have asked few times, but I'm still struggling to find best solution. addAttributes with NSMutableAttributedString won't work as I only can give a link to a part of the string.

I have a label with string "Already sign up? Login here". I can get NSRange of text "Login" but how can I give UITapGesture to it? I don't want to use 3rd party library here. Please suggest me some ideas. Thanks

Upvotes: 0

Views: 1189

Answers (3)

NeverHopeless
NeverHopeless

Reputation: 11233

You can't add tap gesture to a specific part of UILabel or if you tried to do so, you will stuck in finding frames and part of label that might help you at this point but it will be more problematic if it is inside some other view like UITableViewCell etc. It may require some calculation for point conversion with respect to parent/child view.

To keep it simple, my suggestion would be to create a UIView with a UILabel and UIButton embedded in it and button is set next to UILabel.

An example:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

     // Create label
     UILabel *lbl = [[UILabel alloc] init];
    [lbl setText:@"Already Sign up ? "];
    [lbl sizeToFit];

     // Create button
     UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setTitle:@"Login Here" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(didTapLink:) forControlEvents:UIControlEventTouchUpInside];
    [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [btn sizeToFit];
    [btn setFrame:CGRectMake(lbl.frame.size.width, lbl.frame.origin.y, btn.frame.size.width, lbl.frame.size.height)];

     // Create container that embeds the label and button
     UIView *customLabel = [[UIView alloc] initWithFrame:CGRectMake(0, 50, lbl.frame.size.width + btn.frame.size.width, lbl.frame.size.height)];
    [customLabel addSubview:lbl];
    [customLabel addSubview:btn];

     // Add this custom label to UI
    [self.view addSubview:customLabel];
}

-(void)didTapLink:(id)sender
{
    NSLog(@"Button tapped");
}

Screenshot:

enter image description here

Hope that helps!

Upvotes: 0

Tarun Tyagi
Tarun Tyagi

Reputation: 10102

Gesture recognizers can't be added to specific parts of a UI component. However, inside tap handler action, you might want to check

CGPoint tapLocation = [tapRecognizer locationInView:tapRecognizer.view];

You can check if the point lies in your targetRect

if(CGRectContainsPoint(myTargetRect, tapLocation))
{
    // Do your action here
}

Hope it helps.

Update:

Here's the Objective-C version for getRect() from @Code 's answer as you needed.

-(CGRect)getRectForString:(NSAttributedString*)str withRange:(NSRange)textRange usingMaxWidth:(CGFloat)maxWidth
{
    // Create a layoutManager
    NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
    
    // Create a textContainer with maxWidth size and add it to layoutManager
    NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX)];
    [layoutManager addTextContainer:textContainer];
    textContainer.lineFragmentPadding = 0;
    
    // Create a textStorage with 'str' variable and add the layoutManager to this textStorage
    NSTextStorage* textStorage = [[NSTextStorage alloc] initWithAttributedString:str];
    [textStorage addLayoutManager:layoutManager];
    
    // Query the actualGlyphRange from layoutManager for specified textRange
    NSRange actualGlyphRange;
    [layoutManager characterRangeForGlyphRange:textRange actualGlyphRange:&actualGlyphRange];
    
    // Find out the boundingRect for actualGlyphRange
    return [layoutManager boundingRectForGlyphRange:actualGlyphRange inTextContainer:textContainer];
}

Upvotes: 0

christopherdrum
christopherdrum

Reputation: 1523

You cannot add a gesture recognizer to a "part of a UILabel". For your case, I would just use two different UILabels: one that is not-tappable that says "Already signed up?", and one that is tappable that says "Sign in here". Honestly, I'd put the tappable part into a UIButton to make it clear. One more option would be to put an empty UIView as a subview of the UILabel, making it the size of the text you want to tap. Then add a gesture recognizer to THAT subview.

(In my opinion, one of the biggest traps developers get into when working with iOS is trying to force it to act differently than designed; like mimicking a web link or something. It always works best, and with a minimum of code, when written to take advantage of its design patterns)

All of the tap location nonsense can be ignored, because that is the point of a recognizer. The recognizer handles all of that for you, and will call your target with the action you set when the UIView is tapped.

So it would basically be:

UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:someObject action:@selector(someMethod:)];
[loginLabel addGestureRecognizer:tapRecognizer];

And that's it. The rest will be handled for you by the system. Your target object will be called when the label is tapped. At least, it seems simple from the problem as described in the original post. If there are other complications, please include those points in your question.

Upvotes: 1

Related Questions