ios 在 UILabel 的 NSAttributedString 中创建可点击的“链接”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1256887/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:16:39  来源:igfitidea点击:

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

ioshyperlinkuilabelnsattributedstringuitapgesturerecognizer

提问by Lope

I have been searching this for hours but I've failed. I probably don't even know what I should be looking for.

我已经搜索了几个小时,但我失败了。我可能甚至不知道我应该寻找什么。

Many applications have text and in this text are web hyperlinks in rounded rect. When I click them UIWebViewopens. What puzzles me is that they often have custom links, for example if words starts with # it is also clickable and the application responds by opening another view. How can I do that? Is it possible with UILabelor do I need UITextViewor something else?

许多应用程序都有文本,在此文本中是圆角矩形中的网络超链接。当我点击它们UIWebView打开。让我感到困惑的是,他们经常有自定义链接,例如,如果单词以 # 开头,它也是可点击的,应用程序通过打开另一个视图来响应。我怎样才能做到这一点?是否有可能UILabel或我需要UITextView或其他什么?

采纳答案by nalexn

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

一般来说,如果我们希望 UILabel 显示的文本中有一个可点击的链接,我们需要解决两个独立的任务:

  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)
  1. 将部分文本的外观更改为看起来像链接
  2. 检测和处理对链接的触摸(打开 URL 是一种特殊情况)

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:

第一个很容易。从 iOS 6 UILabel 开始支持属性字符串的显示。您需要做的就是创建和配置 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

就是这样!上面的代码使 UILabel 显示带有链接的String

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:

现在我们应该检测这个链接上的触摸。这个想法是捕捉 UILabel 中的所有点击,并确定点击的位置是否足够接近链接。为了捕捉触摸,我们可以向标签添加点击手势识别器。确保为标签启用 userInteraction,默认情况下它是关闭的:

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.

现在是最复杂的东西:找出点击是否在链接显示的地方,而不是在标签的任何其他部分。如果我们有单行 UILabel,这个任务可以通过硬编码显示链接的区域边界来相对容易地解决,但是让我们更优雅地解决这个问题,对于一般情况 - 多行 UILabel 没有关于链接布局的初步知识。

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

其中一种方法是使用 iOS 7 中引入的 Text Kit API 的功能:

// 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.

将创建和配置的 NSLayoutManager、NSTextContainer 和 NSTextStorage 实例保存在类的属性中(很可能是 UIViewController 的后代)——我们将在其他方法中需要它们。

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

现在,每次标签更改其框架时,都会更新 textContainer 的大小:

- (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/"]];
    }
}

回答by samwize

I am extending @NAlexNoriginal detailed solution, with @zekelexcellent extension of UITapGestureRecognizer, and providing in Swift.

我正在扩展@NAlexN原始详细解决方案,@zekel 对 的出色扩展UITapGestureRecognizer,并在Swift 中提供。

Extending UITapGestureRecognizer

扩展 UITapGestureRecognizer

extension UITapGestureRecognizer {

    func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize.zero)
        let textStorage = NSTextStorage(attributedString: label.attributedText!)

        // Configure layoutManager and textStorage
        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        // Configure textContainer
        textContainer.lineFragmentPadding = 0.0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines
        let labelSize = label.bounds.size
        textContainer.size = labelSize

        // Find the tapped character location and compare it to the specified range
        let locationOfTouchInLabel = self.location(in: label)
        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        let textContainerOffset = CGPoint(
            x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
            y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y
        )
        let locationOfTouchInTextContainer = CGPoint(
            x: locationOfTouchInLabel.x - textContainerOffset.x,
            y: locationOfTouchInLabel.y - textContainerOffset.y
        )
        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        return NSLocationInRange(indexOfCharacter, targetRange)
    }

}

Usage

用法

Setup UIGestureRecognizerto send actions to tapLabel:, and you can detect if the target ranges is being tapped on in myLabel.

设置UIGestureRecognizer向 发送操作tapLabel:,您可以检测是否正在点击 中的目标范围myLabel

@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
    if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange1) {
        print("Tapped targetRange1")
    } else if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange2) {
        print("Tapped targetRange2")
    } else {
        print("Tapped none")
    }
}

IMPORTANT: The UILabelline break mode must be set to wrap by word/char. Somehow, NSTextContainerwill assume that the text is single line only if the line break mode is otherwise.

重要提示:UILabel必须将换行模式设置为按字/字符换行。不知何故,NSTextContainer仅当换行模式为其他方式时,才会假定文本为单行。

回答by Kedar Paranjape

Old question but if anyone can use a UITextViewinstead of a UILabel, then it is easy. Standard URLs, phone numbers etc will be automatically detected (and be clickable).

老问题,但如果任何人都可以使用 aUITextView而不是 a UILabel,那么这很容易。标准 URL、电话号码等将被自动检测(并可点击)。

However, if you need custom detection, that is, if you want to be able to call any custom method after a user clicks on a particular word, you need to use NSAttributedStringswith an NSLinkAttributeNameattribute that will point to a custom URL scheme(as opposed to having the http url scheme by default). Ray Wenderlich has it covered here

但是,如果您需要自定义检测,也就是说,如果您希望能够在用户单击特定单词后调用任何自定义方法,则需要使用将指向自定义 URL 方案NSAttributedStringsNSLinkAttributeName属性(而不是默认情况下具有 http url 方案)。雷·温德利希 (Ray Wenderlich) 已在此处介绍

Quoting the code from the aforementioned link:

引用上述链接中的代码:

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"This is an example by @marcelofabri_"];
[attributedString addAttribute:NSLinkAttributeName
                     value:@"username://marcelofabri_"
                     range:[[attributedString string] rangeOfString:@"@marcelofabri_"]];

NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor greenColor],
                             NSUnderlineColorAttributeName: [UIColor lightGrayColor],
                             NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};

// assume that textView is a UITextView previously created (either by code or Interface Builder)
textView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;
textView.delegate = self;

To detect those link clicks, implement this:

要检测这些链接点击,请执行以下操作:

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
    if ([[URL scheme] isEqualToString:@"username"]) {
        NSString *username = [URL host]; 
        // do something with this username
        // ...
        return NO;
    }
    return YES; // let the system open this URL
}

PS: Make sure your UITextViewis selectable.

PS:请确保您UITextViewselectable.

回答by zekel

(My answer builds on @NAlexN's excellent answer. I won't duplicate his detailed explanation of each step here.)

(我的回答建立在@NAlexN 的优秀回答之上。我不会在这里重复他对每个步骤的详细解释。)

I found it most convenient and straightforward to add support for tap-able UILabel text as a category to UITapGestureRecognizer.(You don't haveto use UITextView's data detectors, as some answers suggest.)

我发现为 UITapGestureRecognizer 添加对可点击的 UILabel 文本的支持是最方便和直接的。(你不具备使用的UITextView的数据检测,如一些答案建议。)

Add the following method to your UITapGestureRecognizer category:

将以下方法添加到您的 UITapGestureRecognizer 类别中:

/**
 Returns YES if the tap gesture was within the specified range of the attributed text of the label.
 */
- (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange {
    NSParameterAssert(label != nil);

    CGSize labelSize = label.bounds.size;
    // create instances of NSLayoutManager, NSTextContainer and NSTextStorage
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];

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

    // configure textContainer for the label
    textContainer.lineFragmentPadding = 0.0;
    textContainer.lineBreakMode = label.lineBreakMode;
    textContainer.maximumNumberOfLines = label.numberOfLines;
    textContainer.size = labelSize;

    // find the tapped character location and compare it to the specified range
    CGPoint locationOfTouchInLabel = [self locationInView:label];
    CGRect textBoundingBox = [layoutManager usedRectForTextContainer: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 = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer
                                                            inTextContainer:textContainer
                                   fractionOfDistanceBetweenInsertionPoints:nil];
    if (NSLocationInRange(indexOfCharacter, targetRange)) {
        return YES;
    } else {
        return NO;
    }
}

Example Code

示例代码

// (in your view controller)    
// create your label, gesture recognizer, attributed text, and get the range of the "link" in your label
myLabel.userInteractionEnabled = YES;
[myLabel addGestureRecognizer:
   [[UITapGestureRecognizer alloc] initWithTarget:self 
                                           action:@selector(handleTapOnLabel:)]]; 

// create your attributed text and keep an ivar of your "link" text range
NSAttributedString *plainText;
NSAttributedString *linkText;
plainText = [[NSMutableAttributedString alloc] initWithString:@"Add label links with UITapGestureRecognizer"
                                                   attributes:nil];
linkText = [[NSMutableAttributedString alloc] initWithString:@" Learn more..."
                                                  attributes:@{
                                                      NSForegroundColorAttributeName:[UIColor blueColor]
                                                  }];
NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] init];
[attrText appendAttributedString:plainText];
[attrText appendAttributedString:linkText];

// ivar -- keep track of the target range so you can compare in the callback
targetRange = NSMakeRange(plainText.length, linkText.length);

Gesture Callback

手势回调

// handle the gesture recognizer callback and call the category method
- (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture {
    BOOL didTapLink = [tapGesture didTapAttributedTextInLabel:myLabel
                                            inRange:targetRange];
    NSLog(@"didTapLink: %d", didTapLink);

}

回答by Iggy

The UIButtonTypeCustom is a clickable label if you don't set any images for it.

如果您没有为其设置任何图像,则 UIButtonTypeCustom 是一个可点击的标签。

回答by Charles Gamble

UITextViewsupports data-detectors in OS3.0, whereas UILabeldoesn't.

UITextView在 OS3.0 中支持数据检测器,UILabel但不支持。

If you enable the data-detectors on the UITextViewand your text contains URLs, phone numbers, etc. they will appear as links.

如果您启用数据检测器UITextView并且您的文本包含 URL、电话号码等,它们将显示为链接。

回答by Hernan Arber

Translating @samwize's Extension to Swift 4:

将@samwize 的扩展翻译成 Swift 4:

extension UITapGestureRecognizer {
    func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        guard let attrString = label.attributedText else {
            return false
        }

        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: .zero)
        let textStorage = NSTextStorage(attributedString: attrString)

        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        textContainer.lineFragmentPadding = 0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines
        let labelSize = label.bounds.size
        textContainer.size = labelSize

        let locationOfTouchInLabel = self.location(in: label)
        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
        let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        return NSLocationInRange(indexOfCharacter, targetRange)
    }
}

To set up the recognizer (once you colored the text and stuff):

设置识别器(一旦你为文本和内容着色):

lblTermsOfUse.isUserInteractionEnabled = true
lblTermsOfUse.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOnLabel(_:))))

...then the gesture recognizer:

...然后是手势识别器:

@objc func handleTapOnLabel(_ recognizer: UITapGestureRecognizer) {
    guard let text = lblAgreeToTerms.attributedText?.string else {
        return
    }

    if let range = text.range(of: NSLocalizedString("_onboarding_terms", comment: "terms")),
        recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) {
        goToTermsAndConditions()
    } else if let range = text.range(of: NSLocalizedString("_onboarding_privacy", comment: "privacy")),
        recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) {
        goToPrivacyPolicy()
    }
}

回答by Jinghan Wang

As I mentioned in this post, here is a light-weighted library I created specially for links in UILabel FRHyperLabel.

正如我在这篇文章中提到的,是我专门为 UILabel FRHyperLabel 中的链接创建的轻量级库。

To achieve an effect like this:

要达到这样的效果:

Loremipsum dolor sit amet, consectetur adipiscing elit. Pellentesquequis blanditeros, sit amet vehicula justo. Nam at urna neque. Maecenasac sem eu sem porta dictum nec vel tellus.

Loremipsum dolor 坐 amet,consectetur adipiscing 精英。Pellentesquequis blanditeros, 坐在 amet vehicula justo。Nam at urna neque。Maecenasac sem eu sem porta dictum nec veltellus。

use code:

使用代码:

//Step 1: Define a normal attributed string for non-link texts
NSString *string = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque quis blandit eros, sit amet vehicula justo. Nam at urna neque. Maecenas ac sem eu sem porta dictum nec vel tellus.";
NSDictionary *attributes = @{NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]};

label.attributedText = [[NSAttributedString alloc]initWithString:string attributes:attributes];


//Step 2: Define a selection handler block
void(^handler)(FRHyperLabel *label, NSString *substring) = ^(FRHyperLabel *label, NSString *substring){
    NSLog(@"Selected: %@", substring);
};


//Step 3: Add link substrings
[label setLinksForSubstrings:@[@"Lorem", @"Pellentesque", @"blandit", @"Maecenas"] withLinkHandler:handler];

回答by hsusmita

I created UILabel subclass named ResponsiveLabelwhich is based on textkit API introduced in iOS 7. It uses the same approach suggested by NAlexN. It provides flexibility to specify a pattern to search in the text. One can specify styles to be applied to those patterns as well as action to be performed on tapping the patterns.

我创建了名为ResponsiveLabel 的UILabel 子类,它基于 iOS 7 中引入的 textkit API。它使用与NAlexN建议的相同方法。它可以灵活地指定要在文本中搜索的模式。可以指定要应用于这些模式的样式以及在点击模式时要执行的操作。

//Detects email in text

 NSString *emailRegexString = @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}";
 NSError *error;
 NSRegularExpression *regex = [[NSRegularExpression alloc]initWithPattern:emailRegexString options:0 error:&error];
 PatternDescriptor *descriptor = [[PatternDescriptor alloc]initWithRegex:regex withSearchType:PatternSearchTypeAll withPatternAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]}];
 [self.customLabel enablePatternDetection:descriptor];

If you want to make a string clickable, you can do this way. This code applies attributes to each occurrence of the string "text".

如果你想让一个字符串可点击,你可以这样做。此代码将属性应用于每次出现的字符串“text”。

PatternTapResponder tapResponder = ^(NSString *string) {
    NSLog(@"tapped = %@",string);
};

[self.customLabel enableStringDetection:@"text" withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],
                                                                 RLTapResponderAttributeName: tapResponder}];

回答by mohamede1945

Here is a swift version of NAlexN's answer.

这是 NAlexN 答案的快速版本。

class TapabbleLabel: UILabel {

let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
var textStorage = NSTextStorage() {
    didSet {
        textStorage.addLayoutManager(layoutManager)
    }
}

var onCharacterTapped: ((label: UILabel, characterIndex: Int) -> Void)?

let tapGesture = UITapGestureRecognizer()

override var attributedText: NSAttributedString? {
    didSet {
        if let attributedText = attributedText {
            textStorage = NSTextStorage(attributedString: attributedText)
        } else {
            textStorage = NSTextStorage()
        }
    }
}

override var lineBreakMode: NSLineBreakMode {
    didSet {
        textContainer.lineBreakMode = lineBreakMode
    }
}

override var numberOfLines: Int {
    didSet {
        textContainer.maximumNumberOfLines = numberOfLines
    }
}

/**
 Creates a new view with the passed coder.

 :param: aDecoder The a decoder

 :returns: the created new view.
 */
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setUp()
}

/**
 Creates a new view with the passed frame.

 :param: frame The frame

 :returns: the created new view.
 */
override init(frame: CGRect) {
    super.init(frame: frame)
    setUp()
}

/**
 Sets up the view.
 */
func setUp() {
    userInteractionEnabled = true
    layoutManager.addTextContainer(textContainer)
    textContainer.lineFragmentPadding = 0
    textContainer.lineBreakMode = lineBreakMode
    textContainer.maximumNumberOfLines = numberOfLines
    tapGesture.addTarget(self, action: #selector(TapabbleLabel.labelTapped(_:)))
    addGestureRecognizer(tapGesture)
}

override func layoutSubviews() {
    super.layoutSubviews()
    textContainer.size = bounds.size
}

func labelTapped(gesture: UITapGestureRecognizer) {
    guard gesture.state == .Ended else {
        return
    }

    let locationOfTouch = gesture.locationInView(gesture.view)
    let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
    let textContainerOffset = CGPoint(x: (bounds.width - textBoundingBox.width) / 2 - textBoundingBox.minX,
                                      y: (bounds.height - textBoundingBox.height) / 2 - textBoundingBox.minY)        
    let locationOfTouchInTextContainer = CGPoint(x: locationOfTouch.x - textContainerOffset.x,
                                                 y: locationOfTouch.y - textContainerOffset.y)
    let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer,
                                                                inTextContainer: textContainer,
                                                                fractionOfDistanceBetweenInsertionPoints: nil)

    onCharacterTapped?(label: self, characterIndex: indexOfCharacter)
}
}

You can then create an instance of that class inside your viewDidLoadmethod like this:

然后,您可以在您的viewDidLoad方法中创建该类的实例,如下所示:

let label = TapabbleLabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[view]-|",
                                               options: [], metrics: nil, views: ["view" : label]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[view]-|",
                                               options: [], metrics: nil, views: ["view" : label]))

let attributedString = NSMutableAttributedString(string: "String with a link", attributes: nil)
let linkRange = NSMakeRange(14, 4); // for the word "link" in the string above

let linkAttributes: [String : AnyObject] = [
    NSForegroundColorAttributeName : UIColor.blueColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleSingle.rawValue,
    NSLinkAttributeName: "http://www.apple.com"]
attributedString.setAttributes(linkAttributes, range:linkRange)

label.attributedText = attributedString

label.onCharacterTapped = { label, characterIndex in
    if let attribute = label.attributedText?.attribute(NSLinkAttributeName, atIndex: characterIndex, effectiveRange: nil) as? String,
        let url = NSURL(string: attribute) {
        UIApplication.sharedApplication().openURL(url)
    }
}

It's better to have a custom attribute to use when a character is tapped. Now, it's the NSLinkAttributeName, but could be anything and you can use that value to do other things other than opening a url, you can do any custom action.

最好有一个自定义属性在点击角色时使用。现在,它是NSLinkAttributeName, 但可以是任何东西,除了打开 url 之外,您还可以使用该值来做其他事情,您可以执行任何自定义操作。