22

I have an attributed string UILabel, I was able to color some parts of the text

let text = "Why did \(event) give \(event2) a 5 stars review? Details here. "
let linkTextWithColor = "Why did"
let range = (text as NSString).rangeOfString(linkTextWithColor)

let attributedString = NSMutableAttributedString(string:text)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor() , range: range)

labelEvent.attributedText = attributedString

Now I want to make some parts of the text tappable, like a UIButton, how to do this ?

Example 1

example 1

Example 2

example 2

I need to have blue text to be responding to touch, and to run a specific function, like a UIButton. help is really appreciated, thanks.

Juri Noga
  • 4,363
  • 7
  • 38
  • 51
DeyaEldeen
  • 10,847
  • 10
  • 42
  • 75
  • Here is a link to a couple solutions, I didn't want to take credit :) http://stackoverflow.com/questions/20541676/ios-uitextview-or-uilabel-with-clickable-links-to-actions – Olivier Wilkinson Jun 21 '16 at 11:18
  • http://stackoverflow.com/questions/21629784/make-a-clickable-link-in-an-nsattributedstring-for-a-uitextfield-or-uilabel – Abhay Singh Naurang Jun 21 '16 at 11:21
  • http://stackoverflow.com/questions/1256887/create-tap-able-links-in-the-nsattributedtext-of-a-uilabel look at once – Pushp Jun 21 '16 at 11:35

4 Answers4

15

What you want to do is use an attributed string with a text view to make a link that will act as a button.

let attributedString = NSMutableAttributedString(string: "Some string with link", attributes: [<attributes>])

Then set a part of it as a link, and customize it's appearance using the linkAttributes property of the Text View. Because this is a button and not an actual link we just put a dummy url in for the link so we can handle it in our delegate later.

attributedString.setSubstringAsLink(substring: "link", linkURL: "CUSTOM://WHATEVER")
let linkAttributes: [String : AnyObject] = [NSForegroundColorAttributeName : .redColor(), NSUnderlineColorAttributeName : .redColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleSingle.rawValue]
textView.linkTextAttributes = linkAttributes
textView.attributedText = attributedString
textView.selectable = true
textView.editable = false
textView.userInteractionEnabled = true

Finally in text view delegate we will check for the scheme and perform some action.

func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
    if URL.scheme == "CUSTOM" {
       // Do your button actions here
    }
    return true
}

Extension for setSubstringAsLink:

extension NSMutableAttributedString {
    // Set part of string as URL
    public func setSubstringAsLink(substring substring: String, linkURL: String) -> Bool {
        let range = self.mutableString.rangeOfString(substring)
        if range.location != NSNotFound {
            self.addAttribute(NSLinkAttributeName, value: linkURL, range: range)
            return true
        }
        return false
    }
}
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176
Max
  • 1,143
  • 7
  • 7
  • setSubstringAsLink doesn't exist in whole ios library, this is weird, anybody tried this? – DeyaEldeen Jun 30 '16 at 08:58
  • also, this will require a long tap, which is not good for all cases :( – DeyaEldeen Jun 30 '16 at 11:00
  • Edited with an extension for setting the substring. It does not require a long press. – Max Jun 30 '16 at 12:41
  • I'm getting "Result of call to 'setSubstringAsLink(substring:linkURL:) is unused and therefor shouldInteractWithURL is not being fired. – Felecia Genet Jul 25 '17 at 21:59
  • 1
    This has some side effects. When the link is long pressed it show link text over UITextView which seems to be a UIKit native feature. – Bigair May 31 '20 at 06:47
11

Let's say you have a UILabel with text "abc123", and you want "abc" to function as a UIButton.

  • Calculate and store the rectangle that contains "abc".
  • Add a UITapGestureRecognizer to the UILabel.
  • When the UILabelis tapped, check if the tap is within the rectangle.

static func getRect(str: NSAttributedString, range: NSRange, maxWidth: CGFloat) -> CGRect {
    let textStorage = NSTextStorage(attributedString: str)
    let textContainer = NSTextContainer(size: CGSize(width: maxWidth, height: CGFloat.max))
    let layoutManager = NSLayoutManager()       
    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)     
    textContainer.lineFragmentPadding = 0       
    let pointer = UnsafeMutablePointer<NSRange>.alloc(1)
    layoutManager.characterRangeForGlyphRange(range, actualGlyphRange: pointer)     
    return layoutManager.boundingRectForGlyphRange(pointer.move(), inTextContainer: textContainer)
}

let rect1 = getRect(label.attributedText!, range: NSMakeRange(0, 3), maxWidth: label.frame.width)

label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(MyClass.tappedLabel(_:))))

func tappedLabel(sender: UITapGestureRecognizer) {
    if rect1.contains(sender.locationInView(sender.view)) {
        // ...
    }
}
Code
  • 6,041
  • 4
  • 35
  • 75
9

I recommend you try this library out

https://github.com/null09264/FRHyperLabel

Great library, easy to use and has few built in examples for you to try out. Examples are in both Objective-c and Swift

Example in Swift

 let str = "This is a random bit of text"
        let attributes = [NSForegroundColorAttributeName: UIColor.blackColor(),
                          NSFontAttributeName: UIFont.systemFontOfSize(15)]
        confirmLabel.attributedText = NSAttributedString(string: str, attributes: attributes)

        let handler = {
            (hyperLabel: FRHyperLabel!, substring: String!) -> Void in

            //action here
        }
        //Step 3: Add link substrings
        confirmLabel.setLinksForSubstrings(["random"], withLinkHandler: handler)

Edit:

If you want to get rid of the underline, best way to do this is to follow the advice that DeyaEldeen gave in the comment.

If you go to the .m file of FRHyperLabel, go to this method

- (void)checkInitialization {
    if (!self.handlerDictionary) {
        self.handlerDictionary = [NSMutableDictionary new];
    }

    if (!self.userInteractionEnabled) {
        self.userInteractionEnabled = YES;
    }

    if (!self.linkAttributeDefault) {
        self.linkAttributeDefault = @{NSForegroundColorAttributeName: FRHyperLabelLinkColorDefault,
                                      NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle)};
    }

    if (!self.linkAttributeHighlight) {
        self.linkAttributeHighlight = @{NSForegroundColorAttributeName: FRHyperLabelLinkColorHighlight,
                                        NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle)};
    }
}

And you can just remove this

NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle)

from the attributes

AdamM
  • 4,400
  • 5
  • 49
  • 95
3

The idea is to use library such as TTTAttributedLabel and using it this way :

Consider the following string "I want to be touchable here" where here will perform a segue on touch.

  • Create TTTAttributedLabel (UILabel subclass), and put text content in it, as if it was a UILabel. Set its delegate to self. Then, add a link to a word this way :

Objective C

NSRange rangeWord = [attributedLabel.text rangeOfString:@"here"];
[attributedLabel addLinkToURL:[NSURL URLWithString:@"anActionOnClickHere"] withRange:rangeUser];

Swift

NSRange rangeWord = attributedLabel.text.rangeOfString("here")
attributedLabel.addLinkToURL(NSURL(string: "anActionOnClickHere"), withRange:rangeUser)
  • On clicking words it will call this method in which you can handle the click :

Objective C

- (void)attributedLabel:(__unused TTTAttributedLabel *)label
   didSelectLinkWithURL:(NSURL *)url {
    NSString *urlToString = [url absoluteString];

    if ([urlToString containsString:@"anActionOnClickHere"]) { //perform segue for example
        [self performSegueWithIdentifier:@"hereSegue" sender:self];
    }
}

Swift

func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithURL url: NSURL!) {
    NSString *urlToString = url.absoluteString()
    if (urlToString.containsString("anActionOnClickHere")) { //perform segue for example 
        self.performSegueWithIdentifier("hereSegue",sender:self)
    } 
}

With the default link style, you'll get the blue color that you're looking for.

AnthoPak
  • 4,191
  • 3
  • 23
  • 41