Nsattributedstring Click Event in Uilabel Using Swift

NSAttributedString click event in UILabel using Swift

There's no need to use a separate gesture recognizer as some of the answers state. Instead, you can use attributed text in combination with the UITextViewDelegate's textView:shouldInteractWithURL:inRange:interaction: method to achieve this, ex:

class ViewController: UIViewController, UITextViewDelegate {

@IBOutlet weak var textView: UITextView!

override func viewDidLoad() {
super.viewDidLoad()

let text = NSMutableAttributedString(string: "Already have an account? ")
text.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 12), range: NSMakeRange(0, text.length))

let selectablePart = NSMutableAttributedString(string: "Sign in!")
selectablePart.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 12), range: NSMakeRange(0, selectablePart.length))
// Add an underline to indicate this portion of text is selectable (optional)
selectablePart.addAttribute(NSAttributedString.Key.underlineStyle, value: 1, range: NSMakeRange(0,selectablePart.length))
selectablePart.addAttribute(NSAttributedString.Key.underlineColor, value: UIColor.black, range: NSMakeRange(0, selectablePart.length))
// Add an NSLinkAttributeName with a value of an url or anything else
selectablePart.addAttribute(NSAttributedString.Key.link, value: "signin", range: NSMakeRange(0,selectablePart.length))

// Combine the non-selectable string with the selectable string
text.append(selectablePart)

// Center the text (optional)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = NSTextAlignment.center
text.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, text.length))

// To set the link text color (optional)
textView.linkTextAttributes = [NSAttributedString.Key.foregroundColor:UIColor.black, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12)]
// Set the text view to contain the attributed text
textView.attributedText = text
// Disable editing, but enable selectable so that the link can be selected
textView.isEditable = false
textView.isSelectable = true
// Set the delegate in order to use textView(_:shouldInteractWithURL:inRange)
textView.delegate = self
}

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {

// **Perform sign in action here**

return false
}
}

Create tap-able links in the NSAttributedString of a UILabel?

In general, if we want to have a clickable link in text displayed by UILabel, we would need to resolve two independent tasks:

  1. Changing the appearance of a portion of the text to look like a link
  2. Detecting and handling touches on the link (opening an URL is a particular case)

The first one is easy. Starting from iOS 6 UILabel supports display of attributed strings. All you need to do is to create and configure an instance of NSMutableAttributedString:

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"String with a link" attributes:nil];
NSRange linkRange = NSMakeRange(14, 4); // for the word "link" in the string above

NSDictionary *linkAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithRed:0.05 green:0.4 blue:0.65 alpha:1.0],
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle) };
[attributedString setAttributes:linkAttributes range:linkRange];

// Assign attributedText to UILabel
label.attributedText = attributedString;

That's it! The code above makes UILabel to display String with a link

Now we should detect touches on this link. The idea is to catch all taps within UILabel and figure out whether the location of the tap was close enough to the link. To catch touches we can add tap gesture recognizer to the label. Make sure to enable userInteraction for the label, it's turned off by default:

label.userInteractionEnabled = YES;
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]];

Now the most sophisticated stuff: finding out whether the tap was on where the link is displayed and not on any other portion of the label. If we had single-lined UILabel, this task could be solved relatively easy by hardcoding the area bounds where the link is displayed, but let's solve this problem more elegantly and for general case - multiline UILabel without preliminary knowledge about the link layout.

One of the approaches is to use capabilities of Text Kit API introduced in iOS 7:

// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];

// Configure layoutManager and textStorage
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];

// Configure textContainer
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = label.lineBreakMode;
textContainer.maximumNumberOfLines = label.numberOfLines;

Save created and configured instances of NSLayoutManager, NSTextContainer and NSTextStorage in properties in your class (most likely UIViewController's descendant) - we'll need them in other methods.

Now, each time the label changes its frame, update textContainer's size:

- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.textContainer.size = self.label.bounds.size;
}

And finally, detect whether the tap was exactly on the link:

- (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture
{
CGPoint locationOfTouchInLabel = [tapGesture locationInView:tapGesture.view];
CGSize labelSize = tapGesture.view.bounds.size;
CGRect textBoundingBox = [self.layoutManager usedRectForTextContainer:self.textContainer];
CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
locationOfTouchInLabel.y - textContainerOffset.y);
NSInteger indexOfCharacter = [self.layoutManager characterIndexForPoint:locationOfTouchInTextContainer
inTextContainer:self.textContainer
fractionOfDistanceBetweenInsertionPoints:nil];
NSRange linkRange = NSMakeRange(14, 4); // it's better to save the range somewhere when it was originally used for marking link in attributed string
if (NSLocationInRange(indexOfCharacter, linkRange)) {
// Open an URL, or handle the tap on the link in any other way
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://stackoverflow.com/"]];
}
}

How can I make a clickable link in an NSAttributedString?

I found this really useful but I needed to do it in quite a few places so I've wrapped my approach up in a simple extension to NSMutableAttributedString:

Swift 3

extension NSMutableAttributedString {

public func setAsLink(textToFind:String, linkURL:String) -> Bool {

let foundRange = self.mutableString.range(of: textToFind)
if foundRange.location != NSNotFound {
self.addAttribute(.link, value: linkURL, range: foundRange)
return true
}
return false
}
}

Swift 2

import Foundation

extension NSMutableAttributedString {

public func setAsLink(textToFind:String, linkURL:String) -> Bool {

let foundRange = self.mutableString.rangeOfString(textToFind)
if foundRange.location != NSNotFound {
self.addAttribute(NSLinkAttributeName, value: linkURL, range: foundRange)
return true
}
return false
}
}

Example usage:

let attributedString = NSMutableAttributedString(string:"I love stackoverflow!")
let linkWasSet = attributedString.setAsLink("stackoverflow", linkURL: "http://stackoverflow.com")

if linkWasSet {
// adjust more attributedString properties
}

Objective-C

I've just hit a requirement to do the same in a pure Objective-C project, so here's the Objective-C category.

@interface NSMutableAttributedString (SetAsLinkSupport)

- (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL;

@end


@implementation NSMutableAttributedString (SetAsLinkSupport)

- (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL {

NSRange foundRange = [self.mutableString rangeOfString:textToFind];
if (foundRange.location != NSNotFound) {
[self addAttribute:NSLinkAttributeName value:linkURL range:foundRange];
return YES;
}
return NO;
}

@end

Example usage:

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:"I love stackoverflow!"];

BOOL linkWasSet = [attributedString setAsLink:@"stackoverflow" linkURL:@"http://stackoverflow.com"];

if (linkWasSet) {
// adjust more attributedString properties
}

Make Sure that the NSTextField's Behavior attribute is set as Selectable.
Xcode NSTextField behavior attribute

Add a click event to some text in ios NSString

What if you used the value field to pass in the destination?

[attributedString addAttribute:NSLinkAttributeName
value:[@"destinationController1" stringByAppendingString:username]
range:range];

Then override the delegate method:

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
if ([URL.scheme isEqualToString:@"destinationController1"]) {
// Launch View controller
return NO;
}
return YES;
}

iOS - Swift how to make links clicked in UILabel

As Luke said UILabels dont support URL's. But you can create a button to look like a label. Get rid of the background (so it looks like text )and implement the the function to the button to open the webview. You can use attributed strings to edit the style of the text in the button.

You are going to want to look into using a custom class instead of the default uibutton class when you add the button to your UI.

you will need to:

  1. init

     - (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self)
    {
    //call the method to configure the button
    [self configure];
    }
    return self;
    }
  2. implement setTitle or setAttributedTitle if you are using an attributed string

     - (void) setTitle:(NSString *)title forState:(UIControlState)state
    {
    [super setTitle:title forState:state]
    }

or

      - (void)setAttributedTitle:(NSAttributedString *)title forState:   (UIControlState)state{
[super setAttributedTitle:title forState:state];
}

3. configure the button

         -(void) configure{
//configure the buttons label here
//you will want to set no background colour
}

4. add the button to viewcontroller and change the class to the custom class you created.

How to make tappable NSAttributedString Move To Another ViewController Swift

Try this code to make NSMutableAttributedString, You can make it dynamic as your requirement to pass dynamic string value as a parameter.

Put UItextView insted of UIlabel

@IBOutlet weak var descriptionLbl: UITextView!

descriptionLbl.attributedText = prepareAttributedTextString()

func prepareAttributedTextString() -> NSMutableAttributedString {

let masterString = "dhiman@gmail.com has started following Sham"

let formattedString = NSMutableAttributedString(string: masterString)

let ShamUseAttribute = [
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue,
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16.0),
NSAttributedStringKey.link: URL(string: "Sham")!,
] as [NSAttributedStringKey : Any]


let ShamRange = (masterString as NSString).range(of: "Sham")
formattedString.addAttributes(ShamUseAttribute, range: ShamRange)

return formattedString
}

Use this UITextViewDelegate to detect tap on UItextView and predict to specified UIviewController

// MARK: UITextView Delegate Method
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {

if (URL.absoluteString == "Sham") {
let objVC = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
self.navigationController?.pushViewController(objVC, animated: true)
}
return true
}

Set following property from the storyboard.

Sample Image



Related Topics



Leave a reply



Submit