代码之家  ›  专栏  ›  技术社区  ›  Lope

在uilabel的nsattributedString中创建可点击的“链接”?

  •  196
  • Lope  · 技术社区  · 15 年前

    我搜索这个已经几个小时了,但失败了。我可能根本不知道该找什么。

    许多应用程序都有文本,其中的文本是圆角矩形中的Web超链接。当我点击它们时 UIWebView 打开。令我困惑的是,它们经常有自定义链接,例如,如果单词以开头,它也可以单击,应用程序通过打开另一个视图来响应。我该怎么做?有没有可能 UILabel 或者我需要 UITextView 或者别的什么?

    29 回复  |  直到 6 年前
        1
  •  177
  •   Community CDub    8 年前

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

    1. 将文本的一部分外观更改为链接外观
    2. 检测和处理链接上的触摸(打开URL是一种特殊情况)

    第一个很容易。从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;
    

    就是这样!上面的代码使uilabel显示 带字符串 link

    现在我们应该检测这个链接上的接触。其目的是捕获uilabel中的所有tap,并确定tap的位置是否足够靠近链接。为了捕捉触摸,我们可以在标签中添加轻触手势识别器。确保为标签启用用户交互,默认情况下已将其关闭:

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

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

    其中一种方法是使用iOS 7中引入的文本工具包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;
    

    将创建和配置的nslayoutManager、nstextcontainer和nstextstorage实例保存在类的属性中(很可能是uiviewcontroller的后代),我们在其他方法中需要它们。

    现在,每次标签更改其帧时,更新textcontainer的大小:

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

    最后,检测水龙头是否在连接处:

    - (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/"]];
        }
    }
    
        2
  •  49
  •   samwize    6 年前

    我在延伸 @NAlexN 原始详细解决方案,带 @zekel 优秀的延伸 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.locationInView(label)
            let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
            let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
            let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                locationOfTouchInLabel.y - textContainerOffset.y);
            let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    
            return NSLocationInRange(indexOfCharacter, targetRange)
        }
    
    }
    

    用法

    安装程序 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")
        }
    }
    

    重要事项: UILabel 换行模式必须设置为按word/char换行。不知何故, NSTextContainer 仅当换行模式为其他模式时,才会假定文本为单行。

        3
  •  36
  •   Iggy    15 年前

    uibuttontypecustom是一个可单击的标签,如果您没有为它设置任何图像。

        4
  •  32
  •   Community CDub    8 年前

    (我的回答基于@nalexn's excellent answer . 我不会重复他对每一步的详细解释。)

    我发现将支持可点击的uilabel文本作为一个类别添加到uitapGestureRecognizer是最方便和直接的。 (你不 如某些答案所示,使用UItextView的数据检测器。)

    将以下方法添加到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;
        }
    }
    

    示例代码

    // (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);
    

    手势回拨

    // 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);
    
    }
    
        5
  •  32
  •   Kedar Paranjape    9 年前

    旧问题,但如果有人可以使用 UITextView 而不是 UILabel 那么就很容易了。将自动检测标准URL、电话号码等(并可点击)。

    但是,如果您需要自定义检测,也就是说,如果您希望在用户单击某个特定单词后能够调用任何自定义方法,则需要使用 NSAttributedStrings 用一个 NSLinkAttributeName 属性,该属性将指向自定义的URL方案(而不是默认的HTTP URL方案)。 Ray Wenderlich has it covered here

    引用上述链接中的代码:

    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;
    

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

    - (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:确保您的 UITExtVIEW selectable .

        6
  •  19
  •   Dharmesh Dhorajiya    9 年前

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

    如果您在 UITExtVIEW 您的文本包含URL、电话号码等,它们将显示为链接。

        7
  •  12
  •   glyvox    7 年前

    将@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)
        }
    }
    

    要设置识别器(在对文本和内容着色后):

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

    …然后手势识别器:

    @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()
        }
    }
    
        8
  •  9
  •   Community CDub    8 年前

    正如我在中提到的 this post , 这是我为uilabel中的链接专门创建的一个轻量级库 FRHyperLabel .

    要达到这样的效果:

    Lorem 我是多萝坐阿美,神圣的爱你的精英。 Pellentesque 奎斯 blandit 厄洛斯,坐在阿米特·维希库拉·朱斯托。纳姆在乌尔纳内克。 Maecenas 交流扫描电镜,欧盟扫描电镜,笔记本电脑。

    使用代码:

    //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];
    
        9
  •  7
  •   Community CDub    8 年前

    我创建了名为的uilabel子类 ResponsiveLabel 它基于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];
    

    如果要使字符串可单击,可以这样做。此代码将属性应用于字符串“text”的每次出现。

    PatternTapResponder tapResponder = ^(NSString *string) {
        NSLog(@"tapped = %@",string);
    };
    
    [self.customLabel enableStringDetection:@"text" withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],
                                                                     RLTapResponderAttributeName: tapResponder}];
    
        10
  •  6
  •   Naishta    8 年前

    在Swift 3中工作,在这里粘贴整个代码

        //****Make sure the textview 'Selectable' = checked, and 'Editable = Unchecked'
    
    import UIKit
    
    class ViewController: UIViewController, UITextViewDelegate {
    
        @IBOutlet var theNewTextView: UITextView!
        override func viewDidLoad() {
            super.viewDidLoad()
    
            //****textview = Selectable = checked, and Editable = Unchecked
    
            theNewTextView.delegate = self
    
            let theString = NSMutableAttributedString(string: "Agree to Terms")
            let theRange = theString.mutableString.range(of: "Terms")
    
            theString.addAttribute(NSLinkAttributeName, value: "ContactUs://", range: theRange)
    
            let theAttribute = [NSForegroundColorAttributeName: UIColor.blue, NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue] as [String : Any]
    
            theNewTextView.linkTextAttributes = theAttribute
    
         theNewTextView.attributedText = theString             
    
    theString.setAttributes(theAttribute, range: theRange)
    
        }
    
        func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
    
            if (URL.scheme?.hasPrefix("ContactUs://"))! {
    
                return false //interaction not allowed
            }
    
            //*** Set storyboard id same as VC name
            self.navigationController!.pushViewController((self.storyboard?.instantiateViewController(withIdentifier: "TheLastViewController"))! as UIViewController, animated: true)
    
            return true
        }
    
    }
    
        11
  •  6
  •   Cœur Gustavo Armenta    7 年前

    以下是超链接uilabel的示例代码: 来源: http://sickprogrammersarea.blogspot.in/2014/03/adding-links-to-uilabel.html

    #import "ViewController.h"
    #import "TTTAttributedLabel.h"
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    {
        UITextField *loc;
        TTTAttributedLabel *data;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(5, 20, 80, 25) ];
        [lbl setText:@"Text:"];
        [lbl setFont:[UIFont fontWithName:@"Verdana" size:16]];
        [lbl setTextColor:[UIColor grayColor]];
        loc=[[UITextField alloc] initWithFrame:CGRectMake(4, 20, 300, 30)];
        //loc.backgroundColor = [UIColor grayColor];
        loc.borderStyle=UITextBorderStyleRoundedRect;
        loc.clearButtonMode=UITextFieldViewModeWhileEditing;
        //[loc setText:@"Enter Location"];
        loc.clearsOnInsertion = YES;
        loc.leftView=lbl;
        loc.leftViewMode=UITextFieldViewModeAlways;
        [loc setDelegate:self];
        [self.view addSubview:loc];
        [loc setRightViewMode:UITextFieldViewModeAlways];
        CGRect frameimg = CGRectMake(110, 70, 70,30);
        UIButton *srchButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        srchButton.frame=frameimg;
        [srchButton setTitle:@"Go" forState:UIControlStateNormal];
        [srchButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        srchButton.backgroundColor=[UIColor clearColor];
        [srchButton addTarget:self action:@selector(go:) forControlEvents:UIControlEventTouchDown];
        [self.view addSubview:srchButton];
        data = [[TTTAttributedLabel alloc] initWithFrame:CGRectMake(5, 120,self.view.frame.size.width,200) ];
        [data setFont:[UIFont fontWithName:@"Verdana" size:16]];
        [data setTextColor:[UIColor blackColor]];
        data.numberOfLines=0;
        data.delegate = self;
        data.enabledTextCheckingTypes=NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber;
        [self.view addSubview:data];
    }
    - (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url
    {
        NSString *val=[[NSString alloc]initWithFormat:@"%@",url];
        if ([[url scheme] hasPrefix:@"mailto"]) {
                  NSLog(@" mail URL Selected : %@",url);
            MFMailComposeViewController *comp=[[MFMailComposeViewController alloc]init];
            [comp setMailComposeDelegate:self];
            if([MFMailComposeViewController canSendMail])
            {
                NSString *recp=[[val substringToIndex:[val length]] substringFromIndex:7];
                NSLog(@"Recept : %@",recp);
                [comp setToRecipients:[NSArray arrayWithObjects:recp, nil]];
                [comp setSubject:@"From my app"];
                [comp setMessageBody:@"Hello bro" isHTML:NO];
                [comp setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
                [self presentViewController:comp animated:YES completion:nil];
            }
        }
        else{
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:val]];
        }
    }
    -(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{
        if(error)
        {
            UIAlertView *alrt=[[UIAlertView alloc]initWithTitle:@"Erorr" message:@"Some error occureed" delegate:nil cancelButtonTitle:@"" otherButtonTitles:nil, nil];
            [alrt show];
            [self dismissViewControllerAnimated:YES completion:nil];
        }
        else{
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    }
    
    - (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber
    {
        NSLog(@"Phone Number Selected : %@",phoneNumber);
        UIDevice *device = [UIDevice currentDevice];
        if ([[device model] isEqualToString:@"iPhone"] ) {
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tel:%@",phoneNumber]]];
        } else {
            UIAlertView *Notpermitted=[[UIAlertView alloc] initWithTitle:@"Alert" message:@"Your device doesn't support this feature." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [Notpermitted show];
        }
    }
    -(void)go:(id)sender
    {
        [data setText:loc.text];
    }
    
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"Reached");
        [loc resignFirstResponder];
    }
    
        12
  •  4
  •   mohamede1945    9 年前

    这是纳勒森答案的快速版本。

    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)
    }
    

    }

    然后您可以在 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)
        }
    }
    

    当一个字符被点击时,最好有一个自定义属性来使用。现在,它是 NSLinkAttributeName ,但可以是任何内容,您可以使用该值执行除打开URL之外的其他操作,您可以执行任何自定义操作。

        13
  •  4
  •   Edwin Vermeer    8 年前

    正如在早期的awnser中报告的那样,uitextview能够处理链接上的触摸。这可以通过使文本的其他部分作为链接进行扩展。attributedTextView库是一个uitextView子类,可以很容易地处理这些子类。有关详细信息,请参阅: https://github.com/evermeer/AttributedTextView

    您可以使文本的任何部分像这样交互(其中textview1是uitextview iboutlet):

    textView1.attributer =
        "1. ".red
        .append("This is the first test. ").green
        .append("Click on ").black
        .append("evict.nl").makeInteract { _ in
            UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { completed in })
        }.underline
        .append(" for testing links. ").black
        .append("Next test").underline.makeInteract { _ in
            print("NEXT")
        }
        .all.font(UIFont(name: "SourceSansPro-Regular", size: 16))
        .setLinkColor(UIColor.purple) 
    

    对于处理散列标签和注释,可以使用如下代码:

    textView1.attributer = "@test: What #hashtags do we have in @evermeer #AtributedTextView library"
        .matchHashtags.underline
        .matchMentions
        .makeInteract { link in
            UIApplication.shared.open(URL(string: "https://twitter.com\(link.replacingOccurrences(of: "@", with: ""))")!, options: [:], completionHandler: { completed in })
        }
    
        14
  •  3
  •   Oscar Salguero    9 年前

    我很难处理这件事…带有属性文本上链接的uilabel…只是头痛,所以我最后用了 ZSWTappableLabel .

        15
  •  3
  •   timbroder    8 年前

    我在扩展@samwize的答案以处理多行uilabel,并给出一个使用uibutton的示例

    extension UITapGestureRecognizer {
    
        func didTapAttributedTextInButton(button: UIButton, inRange targetRange: NSRange) -> Bool {
            guard let label = button.titleLabel else { return false }
            return didTapAttributedTextInLabel(label, inRange: targetRange)
        }
    
        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.locationInView(label)
            let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
            let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                                  (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
            let locationOfTouchInTextContainer = CGPointMake((locationOfTouchInLabel.x - textContainerOffset.x),
                                                             0 );
            // Adjust for multiple lines of text
            let lineModifier = Int(ceil(locationOfTouchInLabel.y / label.font.lineHeight)) - 1
            let rightMostFirstLinePoint = CGPointMake(labelSize.width, 0)
            let charsPerLine = layoutManager.characterIndexForPoint(rightMostFirstLinePoint, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    
            let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
            let adjustedRange = indexOfCharacter + (lineModifier * charsPerLine)
    
            return NSLocationInRange(adjustedRange, targetRange)
        }
    
    }
    
        16
  •  2
  •   Kendall Helmstetter Gelner    15 年前

    对于完全自定义的链接,您需要使用uiwebview-您可以截取调用,以便在按下链接时转到应用程序的其他部分。

        17
  •  2
  •   Community CDub    8 年前

    以下是目标C类别中的一个下拉项,它可以在现有的 UILabel.attributedText 字符串,利用现有的 NSLinkAttributeName 属性。

    @interface UILabel (GSBClickableLinks) <UIGestureRecognizerDelegate>
    @property BOOL enableLinks;
    @end
    
    #import <objc/runtime.h>
    static const void *INDEX;
    static const void *TAP;
    
    @implementation UILabel (GSBClickableLinks)
    
    - (void)setEnableLinks:(BOOL)enableLinks
    {
        UITapGestureRecognizer *tap = objc_getAssociatedObject(self, &TAP); // retreive tap
        if (enableLinks && !tap) { // add a gestureRegonzier to the UILabel to detect taps
            tap = [UITapGestureRecognizer.alloc initWithTarget:self action:@selector(openLink)];
            tap.delegate = self;
            [self addGestureRecognizer:tap];
            objc_setAssociatedObject(self, &TAP, tap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // save tap
        }
        self.userInteractionEnabled = enableLinks; // note - when false UILAbel wont receive taps, hence disable links
    }
    
    - (BOOL)enableLinks
    {
        return (BOOL)objc_getAssociatedObject(self, &TAP); // ie tap != nil
    }
    
    // First check whether user tapped on a link within the attributedText of the label.
    // If so, then the our label's gestureRecogizer will subsequently fire, and open the corresponding NSLinkAttributeName.
    // If not, then the tap will get passed along, eg to the enclosing UITableViewCell...
    // Note: save which character in the attributedText was clicked so that we dont have to redo everything again in openLink.
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
    {
        if (gestureRecognizer != objc_getAssociatedObject(self, &TAP)) return YES; // dont block other gestures (eg swipe)
    
        // Re-layout the attributedText to find out what was tapped
        NSTextContainer *textContainer = [NSTextContainer.alloc initWithSize:self.frame.size];
        textContainer.lineFragmentPadding = 0;
        textContainer.maximumNumberOfLines = self.numberOfLines;
        textContainer.lineBreakMode = self.lineBreakMode;
        NSLayoutManager *layoutManager = NSLayoutManager.new;
        [layoutManager addTextContainer:textContainer];
        NSTextStorage *textStorage = [NSTextStorage.alloc initWithAttributedString:self.attributedText];
        [textStorage addLayoutManager:layoutManager];
    
        NSUInteger index = [layoutManager characterIndexForPoint:[gestureRecognizer locationInView:self]
                                                 inTextContainer:textContainer
                        fractionOfDistanceBetweenInsertionPoints:NULL];
        objc_setAssociatedObject(self, &INDEX, @(index), OBJC_ASSOCIATION_RETAIN_NONATOMIC); // save index
    
        return (BOOL)[self.attributedText attribute:NSLinkAttributeName atIndex:index effectiveRange:NULL]; // tapped on part of a link?
    }
    
    - (void)openLink
    {
        NSUInteger index = [objc_getAssociatedObject(self, &INDEX) unsignedIntegerValue]; // retrieve index
        NSURL *url = [self.attributedText attribute:NSLinkAttributeName atIndex:index effectiveRange:NULL];
        if (url && [UIApplication.sharedApplication canOpenURL:url]) [UIApplication.sharedApplication openURL:url];
    }
    
    @end 
    

    通过uilabel子类(即没有objc_getassociatedobject mess)可以更清楚地完成这一点,但是如果您像我一样,您希望避免为了向现有uikit类添加一些额外的函数而生成不必要的(第三方)子类。此外,它还提供了可点击链接的优点 任何 现有的uilabel,例如现有的 UITableViewCells !

    我试图通过使用现有的 nslinkattributename(nslinkattributename) 属性资料已经在nsattributedstring中可用。所以它很简单:

    NSURL *myURL = [NSURL URLWithString:@"http://www.google.com"];
    NSMutableAttributedString *myString = [NSMutableAttributedString.alloc initWithString:@"This string has a clickable link: "];
    [myString appendAttributedString:[NSAttributedString.alloc initWithString:@"click here" attributes:@{NSLinkAttributeName:myURL}]];
    ...
    myLabel.attributedText = myString;
    myLabel.enableLinks = YES; // yes, that's all! :-)
    

    基本上,它通过添加 UIGestureRecognizer 到您的uilabel。艰苦的工作是在 gestureRecognizerShouldBegin: 重新布局attributedtext字符串,以找出所点击的字符。如果此字符是nslinkattributename的一部分,则GestureRecognizer随后将激发,检索相应的url(从nslinkattributename值中),并按照通常的方式打开链接 [UIApplication.sharedApplication openURL:url] 过程。

    注意-在 GestureRecognizers应该开始: ,如果您没有碰巧点击标签中的链接,则事件将被传递。因此,例如,uiTableViewCell将捕获对链接的点击,但在其他情况下行为正常(选择单元格、取消选择、滚动…)。

    我把它放在Github存储库中了 here . 改编自Kai Burghardt的So Post here .

        18
  •  2
  •   Eric Aya    6 年前

    我遵循这个版本,

    斯威夫特4:

    import Foundation
    
    class AELinkedClickableUILabel: UILabel {
    
        typealias YourCompletion = () -> Void
    
        var linkedRange: NSRange!
        var completion: YourCompletion?
    
        @objc func linkClicked(sender: UITapGestureRecognizer){
    
            if let completionBlock = completion {
    
                let textView = UITextView(frame: self.frame)
                textView.text = self.text
                textView.attributedText = self.attributedText
                let index = textView.layoutManager.characterIndex(for: sender.location(in: self),
                                                                  in: textView.textContainer,
                                                                  fractionOfDistanceBetweenInsertionPoints: nil)
    
                if linkedRange.lowerBound <= index && linkedRange.upperBound >= index {
    
                    completionBlock()
                }
            }
        }
    
    /**
     *  This method will be used to set an attributed text specifying the linked text with a
     *  handler when the link is clicked
     */
        public func setLinkedTextWithHandler(text:String, link: String, handler: @escaping ()->()) -> Bool {
    
            let attributextText = NSMutableAttributedString(string: text)
            let foundRange = attributextText.mutableString.range(of: link)
    
            if foundRange.location != NSNotFound {
                self.linkedRange = foundRange
                self.completion = handler
                attributextText.addAttribute(NSAttributedStringKey.link, value: text, range: foundRange)
                self.isUserInteractionEnabled = true
                self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(linkClicked(sender:))))
                return true
            }
            return false
        }
    }
    

    调用示例:

    button.setLinkedTextWithHandler(text: "This website (stackoverflow.com) is awesome", link: "stackoverflow.com") 
    {
        // show popup or open to link
    }
    
        19
  •  1
  •   jjpp    9 年前

    使用以下.h和.m文件创建类。在.m文件中有以下函数

     - (void)linkAtPoint:(CGPoint)location
    

    在这个函数中,我们将检查需要为其执行操作的子字符串的范围。使用您自己的逻辑来设置范围。

    下面是子类的用法

    TaggedLabel *label = [[TaggedLabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    [self.view addSubview:label];
    label.numberOfLines = 0;
    NSMutableAttributedString *attributtedString = [[NSMutableAttributedString alloc] initWithString : @"My name is @jjpp" attributes : @{ NSFontAttributeName : [UIFont systemFontOfSize:10],}];                                                                                                                                                                              
    //Do not forget to add the font attribute.. else it wont work.. it is very important
    [attributtedString addAttribute:NSForegroundColorAttributeName
                            value:[UIColor redColor]
                            range:NSMakeRange(11, 5)];//you can give this range inside the .m function mentioned above
    

    以下是.h文件

    #import <UIKit/UIKit.h>
    
    @interface TaggedLabel : UILabel<NSLayoutManagerDelegate>
    
    @property(nonatomic, strong)NSLayoutManager *layoutManager;
    @property(nonatomic, strong)NSTextContainer *textContainer;
    @property(nonatomic, strong)NSTextStorage *textStorage;
    @property(nonatomic, strong)NSArray *tagsArray;
    @property(readwrite, copy) tagTapped nameTagTapped;
    
    @end   
    

    以下是.m文件

    #import "TaggedLabel.h"
    @implementation TaggedLabel
    
    - (id)initWithFrame:(CGRect)frame
    {
     self = [super initWithFrame:frame];
     if (self)
     {
      self.userInteractionEnabled = YES;
     }
    return self;
    }
    
    - (id)initWithCoder:(NSCoder *)aDecoder
    {
     self = [super initWithCoder:aDecoder];
    if (self)
    {
     self.userInteractionEnabled = YES;
    }
    return self;
    }
    
    - (void)setupTextSystem
    {
     _layoutManager = [[NSLayoutManager alloc] init];
     _textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
     _textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
     // Configure layoutManager and textStorage
     [_layoutManager addTextContainer:_textContainer];
     [_textStorage addLayoutManager:_layoutManager];
     // Configure textContainer
     _textContainer.lineFragmentPadding = 0.0;
     _textContainer.lineBreakMode = NSLineBreakByWordWrapping;
     _textContainer.maximumNumberOfLines = 0;
     self.userInteractionEnabled = YES;
     self.textContainer.size = self.bounds.size;
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
     if (!_layoutManager)
     {
      [self setupTextSystem];
     }
     // Get the info for the touched link if there is one
     CGPoint touchLocation = [[touches anyObject] locationInView:self];
     [self linkAtPoint:touchLocation];
    }
    
    - (void)linkAtPoint:(CGPoint)location
    {
     // Do nothing if we have no text
     if (_textStorage.string.length == 0)
     {
      return;
     }
     // Work out the offset of the text in the view
     CGPoint textOffset = [self calcGlyphsPositionInView];
     // Get the touch location and use text offset to convert to text cotainer coords
     location.x -= textOffset.x;
     location.y -= textOffset.y;
     NSUInteger touchedChar = [_layoutManager glyphIndexForPoint:location inTextContainer:_textContainer];
     // If the touch is in white space after the last glyph on the line we don't
     // count it as a hit on the text
     NSRange lineRange;
     CGRect lineRect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex:touchedChar effectiveRange:&lineRange];
     if (CGRectContainsPoint(lineRect, location) == NO)
     {
      return;
     }
     // Find the word that was touched and call the detection block
        NSRange range = NSMakeRange(11, 5);//for this example i'm hardcoding the range here. In a real scenario it should be iterated through an array for checking all the ranges
        if ((touchedChar >= range.location) && touchedChar < (range.location + range.length))
        {
         NSLog(@"range-->>%@",self.tagsArray[i][@"range"]);
        }
    }
    
    - (CGPoint)calcGlyphsPositionInView
    {
     CGPoint textOffset = CGPointZero;
     CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
     textBounds.size.width = ceil(textBounds.size.width);
     textBounds.size.height = ceil(textBounds.size.height);
    
     if (textBounds.size.height < self.bounds.size.height)
     {
      CGFloat paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2.0;
      textOffset.y = paddingHeight;
     }
    
     if (textBounds.size.width < self.bounds.size.width)
     {
      CGFloat paddingHeight = (self.bounds.size.width - textBounds.size.width) / 2.0;
      textOffset.x = paddingHeight;
     }
     return textOffset;
     }
    
    @end
    
        20
  •  1
  •   Lukasz Czerwinski    9 年前

    我强烈建议使用自动检测文本中的URL并将其转换为链接的库。 尝试:

    两者都是麻省理工学院的许可证。

        21
  •  1
  •   user1105951    9 年前

    根据Charles Gamble的回答,我使用了以下方法(我删除了一些让我困惑的行,并给了我错误的索引):

    - (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange TapGesture:(UIGestureRecognizer*) gesture{
        NSParameterAssert(label != nil);
    
        // create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
        NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
    
        // configure layoutManager and textStorage
        [textStorage addLayoutManager:layoutManager];
    
        // configure textContainer for the label
        NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(label.frame.size.width, label.frame.size.height)];
    
        textContainer.lineFragmentPadding = 0.0;
        textContainer.lineBreakMode = label.lineBreakMode;
        textContainer.maximumNumberOfLines = label.numberOfLines;
    
        // find the tapped character location and compare it to the specified range
        CGPoint locationOfTouchInLabel = [gesture locationInView:label];
        [layoutManager addTextContainer:textContainer]; //(move here, not sure it that matter that calling this line after textContainer is set
    
        NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInLabel
                                                               inTextContainer:textContainer
                                      fractionOfDistanceBetweenInsertionPoints:nil];
        if (NSLocationInRange(indexOfCharacter, targetRange)) {
            return YES;
        } else {
            return NO;
        }
    }
    
        22
  •  1
  •   mxcl    7 年前

    这里的_是一个尽可能少的快速实现,还包括触摸反馈。Caveats:

    1. 必须在NSattributedStrings中设置字体
    2. 只能使用NSattributedStrings!
    3. 必须确保链接不能换行(使用不间断空格: "\u{a0}" )
    4. 设置文本后不能更改换行模式或换行数
    5. 通过添加属性创建链接 .link 钥匙

    .

    public class LinkLabel: UILabel {
        private var storage: NSTextStorage?
        private let textContainer = NSTextContainer()
        private let layoutManager = NSLayoutManager()
        private var selectedBackgroundView = UIView()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            textContainer.lineFragmentPadding = 0
            layoutManager.addTextContainer(textContainer)
            textContainer.layoutManager = layoutManager
            isUserInteractionEnabled = true
            selectedBackgroundView.isHidden = true
            selectedBackgroundView.backgroundColor = UIColor(white: 0, alpha: 0.3333)
            selectedBackgroundView.layer.cornerRadius = 4
            addSubview(selectedBackgroundView)
        }
    
        public required convenience init(coder: NSCoder) {
            self.init(frame: .zero)
        }
    
        public override func layoutSubviews() {
            super.layoutSubviews()
            textContainer.size = frame.size
        }
    
        public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesBegan(touches, with: event)
            setLink(for: touches)
        }
    
        public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesMoved(touches, with: event)
            setLink(for: touches)
        }
    
        private func setLink(for touches: Set<UITouch>) {
            if let pt = touches.first?.location(in: self), let (characterRange, _) = link(at: pt) {
                let glyphRange = layoutManager.glyphRange(forCharacterRange: characterRange, actualCharacterRange: nil)
                selectedBackgroundView.frame = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer).insetBy(dx: -3, dy: -3)
                selectedBackgroundView.isHidden = false
            } else {
                selectedBackgroundView.isHidden = true
            }
        }
    
        public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesCancelled(touches, with: event)
            selectedBackgroundView.isHidden = true
        }
    
        public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesEnded(touches, with: event)
            selectedBackgroundView.isHidden = true
    
            if let pt = touches.first?.location(in: self), let (_, url) = link(at: pt) {
                UIApplication.shared.open(url)
            }
        }
    
        private func link(at point: CGPoint) -> (NSRange, URL)? {
            let touchedGlyph = layoutManager.glyphIndex(for: point, in: textContainer)
            let touchedChar = layoutManager.characterIndexForGlyph(at: touchedGlyph)
            var range = NSRange()
            let attrs = attributedText!.attributes(at: touchedChar, effectiveRange: &range)
            if let urlstr = attrs[.link] as? String {
                return (range, URL(string: urlstr)!)
            } else {
                return nil
            }
        }
    
        public override var attributedText: NSAttributedString? {
            didSet {
                textContainer.maximumNumberOfLines = numberOfLines
                textContainer.lineBreakMode = lineBreakMode
                if let txt = attributedText {
                    storage = NSTextStorage(attributedString: txt)
                    storage!.addLayoutManager(layoutManager)
                    layoutManager.textStorage = storage
                    textContainer.size = frame.size
                }
            }
        }
    }
    
        23
  •  1
  •   Naishta    7 年前

    这个通用方法也可以工作!

    func didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange targetRange: NSRange) -> Bool {
    
            let layoutManager = NSLayoutManager()
            let textContainer = NSTextContainer(size: CGSize.zero)
            guard let strAttributedText = self.attributedText else {
                return false
            }
    
            let textStorage = NSTextStorage(attributedString: strAttributedText)
    
            // Configure layoutManager and textStorage
            layoutManager.addTextContainer(textContainer)
            textStorage.addLayoutManager(layoutManager)
    
            // Configure textContainer
            textContainer.lineFragmentPadding = Constants.lineFragmentPadding
            textContainer.lineBreakMode = self.lineBreakMode
            textContainer.maximumNumberOfLines = self.numberOfLines
            let labelSize = self.bounds.size
            textContainer.size = CGSize(width: labelSize.width, height: CGFloat.greatestFiniteMagnitude)
    
            // Find the tapped character location and compare it to the specified range
            let locationOfTouchInLabel = gesture.location(in: self)
    
            let xCordLocationOfTouchInTextContainer = locationOfTouchInLabel.x
            let yCordLocationOfTouchInTextContainer = locationOfTouchInLabel.y
            let locOfTouch = CGPoint(x: xCordLocationOfTouchInTextContainer ,
                                     y: yCordLocationOfTouchInTextContainer)
    
            let indexOfCharacter = layoutManager.characterIndex(for: locOfTouch, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    
            guard let strLabel = text else {
                return false
            }
    
            let charCountOfLabel = strLabel.count
    
            if indexOfCharacter < (charCountOfLabel - 1) {
                return NSLocationInRange(indexOfCharacter, targetRange)
            } else {
                return false
            }
        }
    

    你可以用

    let text = yourLabel.text
    let termsRange = (text as NSString).range(of: fullString)
    if yourLabel.didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange: termsRange) {
                showCorrespondingViewController()
            }
    
        24
  •  1
  •   Jeremy Vandermeersch    6 年前

    我找到了另一个解决方案:

    我找到了一种方法,可以检测从Internet上找到的HTML文本中的链接,并将其转换为nsattributeString,方法是:

    func htmlAttributedString(fontSize: CGFloat = 17.0) -> NSAttributedString? {
                let fontName = UIFont.systemFont(ofSize: fontSize).fontName
                let string = self.appending(String(format: "<style>body{font-family: '%@'; font-size:%fpx;}</style>", fontName, fontSize))
                guard let data = string.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
    
                guard let html = try? NSMutableAttributedString (
                    data: data,
                    options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html],
                    documentAttributes: nil) else { return nil }
                return html
            }
    

    我的方法允许您检测超链接,而不必指定它们。

    • 首先,创建TapGestureRecognizer的扩展:

      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)
      }
      

      }

    然后,在视图控制器中,您创建了一个URL和范围列表,以存储所有链接以及属性文本包含的范围:

    var listurl : [String] = []
        var listURLRange : [NSRange] = []
    

    要查找URL和urlrange,可以使用:

        fun findLinksAndRange(attributeString : NSAttributeString){
            notification.enumerateAttribute(NSAttributedStringKey.link , in: NSMakeRange(0, notification.length), options: [.longestEffectiveRangeNotRequired]) { value, range, isStop in
                        if let value = value {
                            print("\(value) found at \(range.location)")
                            let stringValue = "\(value)"
                            listurl.append(stringValue)
                            listURLRange.append(range)
                        }
                    }
    
                westlandNotifcationLabel.addGestureRecognizer(UITapGestureRecognizer(target : self, action: #selector(handleTapOnLabel(_:))))
    
        }
    

    然后执行句柄tap:

    @objc func handleTapOnLabel(_ recognizer: UITapGestureRecognizer) {
            for index in 0..<listURLRange.count{
                if recognizer.didTapAttributedTextInLabel(label: westlandNotifcationLabel, inRange: listURLRange[index]) {
                    goToWebsite(url : listurl[index])
                }
            }
        }
    
        func goToWebsite(url : String){
            if let websiteUrl = URL(string: url){
                if #available(iOS 10, *) {
                    UIApplication.shared.open(websiteUrl, options: [:],
                                              completionHandler: {
                                                (success) in
                                                print("Open \(websiteUrl): \(success)")
                    })
                } else {
                    let success = UIApplication.shared.openURL(websiteUrl)
                    print("Open \(websiteUrl): \(success)")
                }
            }
        }
    

    我们走!

    我希望这个解决方案能帮助你,就像它能帮助我一样。

        25
  •  0
  •   Luca Davanzo    9 年前

    标签 SWIFT2.0

    我从Nalexn的答案中得到灵感,我决定自己写一个uilabel的包装纸。
    我也尝试过 TTTAttributedLabel 但我做不到。

    希望您能欣赏此代码,欢迎您提出任何建议!

    import Foundation
    
    @objc protocol TappableLabelDelegate {
        optional func tappableLabel(tabbableLabel: TappableLabel, didTapUrl: NSURL, atRange: NSRange)
    }
    
    /// Represent a label with attributed text inside.
    /// We can add a correspondence between a range of the attributed string an a link (URL)
    /// By default, link will be open on the external browser @see 'openLinkOnExternalBrowser'
    
    class TappableLabel: UILabel {
    
        // MARK: - Public properties -
    
        var links: NSMutableDictionary = [:]
        var openLinkOnExternalBrowser = true
        var delegate: TappableLabelDelegate?
    
        // MARK: - Constructors -
    
        override func awakeFromNib() {
            super.awakeFromNib()
            self.enableInteraction()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.enableInteraction()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        private func enableInteraction() {
            self.userInteractionEnabled = true
            self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("didTapOnLabel:")))
        }
    
        // MARK: - Public methods -
    
        /**
        Add correspondence between a range and a link.
    
        - parameter url:   url.
        - parameter range: range on which couple url.
        */
        func addLink(url url: String, atRange range: NSRange) {
            self.links[url] = range
        }
    
        // MARK: - Public properties -
    
        /**
        Action rised on user interaction on label.
    
        - parameter tapGesture: gesture.
        */
        func didTapOnLabel(tapGesture: UITapGestureRecognizer) {
            let labelSize = self.bounds.size;
    
            let layoutManager = NSLayoutManager()
            let textContainer = NSTextContainer(size: CGSizeZero)
            let textStorage = NSTextStorage(attributedString: self.attributedText!)
    
            // configure textContainer for the label
            textContainer.lineFragmentPadding = 0
            textContainer.lineBreakMode = self.lineBreakMode
            textContainer.maximumNumberOfLines = self.numberOfLines
            textContainer.size = labelSize;
    
            // configure layoutManager and textStorage
            layoutManager.addTextContainer(textContainer)
            textStorage.addLayoutManager(layoutManager)
    
            // find the tapped character location and compare it to the specified range
            let locationOfTouchInLabel = tapGesture.locationInView(self)
    
            let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
            let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
            let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                locationOfTouchInLabel.y - textContainerOffset.y)
            let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer,
                inTextContainer:textContainer,
                fractionOfDistanceBetweenInsertionPoints: nil)
    
            for (url, value) in self.links {
                if let range = value as? NSRange {
                    if NSLocationInRange(indexOfCharacter, range) {
                        let url = NSURL(string: url as! String)!
                        if self.openLinkOnExternalBrowser {
                            UIApplication.sharedApplication().openURL(url)
                        }
                        self.delegate?.tappableLabel?(self, didTapUrl: url, atRange: range)
                    }
                }
            }
        }
    
    }
    
        26
  •  0
  •   Pol    8 年前

    将解决方案作为类别放到 UILabel (这假设 乌拉贝尔 将属性化字符串与 NSLinkAttributeName 其中的属性):

    @implementation UILabel (Support)
    
    - (BOOL)openTappedLinkAtLocation:(CGPoint)location {
      CGSize labelSize = self.bounds.size;
    
      NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
      textContainer.lineFragmentPadding = 0.0;
      textContainer.lineBreakMode = self.lineBreakMode;
      textContainer.maximumNumberOfLines = self.numberOfLines;
      textContainer.size = labelSize;
    
      NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
      [layoutManager addTextContainer:textContainer];
    
      NSTextStorage* textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
      [textStorage addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, textStorage.length)];
      [textStorage addLayoutManager:layoutManager];
    
      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(location.x - textContainerOffset.x, location.y - textContainerOffset.y);
      NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nullptr];
      if (indexOfCharacter >= 0) {
        NSURL* url = [textStorage attribute:NSLinkAttributeName atIndex:indexOfCharacter effectiveRange:nullptr];
        if (url) {
          [[UIApplication sharedApplication] openURL:url];
          return YES;
        }
      }
      return NO;
    }
    
    @end
    
        27
  •  0
  •   HamGuy    6 年前

    Here 我的答案是基于@luca davanzo的 answer 重写 touchesBegan 事件而不是点击手势:

    import UIKit
    
    public protocol TapableLabelDelegate: NSObjectProtocol {
       func tapableLabel(_ label: TapableLabel, didTapUrl url: String, atRange range: NSRange)
    }
    
    public class TapableLabel: UILabel {
    
    private var links: [String: NSRange] = [:]
    private(set) var layoutManager = NSLayoutManager()
    private(set) var textContainer = NSTextContainer(size: CGSize.zero)
    private(set) var textStorage = NSTextStorage() {
        didSet {
            textStorage.addLayoutManager(layoutManager)
        }
    }
    
    public weak var delegate: TapableLabelDelegate?
    
    public override var attributedText: NSAttributedString? {
        didSet {
            if let attributedText = attributedText {
                textStorage = NSTextStorage(attributedString: attributedText)
            } else {
                textStorage = NSTextStorage()
                links = [:]
            }
        }
    }
    
    public override var lineBreakMode: NSLineBreakMode {
        didSet {
            textContainer.lineBreakMode = lineBreakMode
        }
    }
    
    public override var numberOfLines: Int {
        didSet {
            textContainer.maximumNumberOfLines = numberOfLines
        }
    }
    
    
    public override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        textContainer.size = bounds.size
    }
    
    
    /// addLinks
    ///
    /// - Parameters:
    ///   - text: text of link
    ///   - url: link url string
    public func addLink(_ text: String, withURL url: String) {
        guard let theText = attributedText?.string as? NSString else {
            return
        }
    
        let range = theText.range(of: text)
    
        guard range.location !=  NSNotFound else {
            return
        }
    
        links[url] = range
    }
    
    private func setup() {
        isUserInteractionEnabled = true
        layoutManager.addTextContainer(textContainer)
        textContainer.lineFragmentPadding = 0
        textContainer.lineBreakMode = lineBreakMode
        textContainer.maximumNumberOfLines  = numberOfLines
    }
    
    public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let locationOfTouch = touches.first?.location(in: self) else {
            return
        }
    
        textContainer.size = bounds.size
        let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)
    
        for (urlString, range) in links {
            if NSLocationInRange(indexOfCharacter, range), let url = URL(string: urlString) {
                delegate?.tapableLabel(self, didTapUrl: urlString, atRange: range)
            }
        }
    }}
    
        28
  •  0
  •   user3044484    6 年前
    - (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange{
        NSLayoutManager *layoutManager = [NSLayoutManager new];
        NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
        NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
    
        [layoutManager addTextContainer:textContainer];
        [textStorage addLayoutManager:layoutManager];
    
        textContainer.lineFragmentPadding = 0.0;
        textContainer.lineBreakMode = label.lineBreakMode;
        textContainer.maximumNumberOfLines = label.numberOfLines;
        CGSize labelSize = label.bounds.size;
        textContainer.size = labelSize;
    
        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);
        NSUInteger indexOfCharacter =[layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nil];
    
        return NSLocationInRange(indexOfCharacter, targetRange);
    }
    
        29
  •  -2
  •   Pang Ajmal PraveeN    9 年前
        NSString *string = name;
        NSError *error = NULL;
        NSDataDetector *detector =
        [NSDataDetector dataDetectorWithTypes:(NSTextCheckingTypes)NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber
                                        error:&error];
        NSArray *matches = [detector matchesInString:string
                                             options:0
                                               range:NSMakeRange(0, [string length])];
        for (NSTextCheckingResult *match in matches)
        {
            if (([match resultType] == NSTextCheckingTypePhoneNumber))
            {
                NSString *phoneNumber = [match phoneNumber];
                NSLog(@" Phone Number is :%@",phoneNumber);
                label.enabledTextCheckingTypes = NSTextCheckingTypePhoneNumber;
            }
    
            if(([match resultType] == NSTextCheckingTypeLink))
            {
                NSURL *email = [match URL];
                NSLog(@"Email is  :%@",email);
                label.enabledTextCheckingTypes = NSTextCheckingTypeLink;
            }
    
            if (([match resultType] == NSTextCheckingTypeLink))
            {
                NSURL *url = [match URL];
                NSLog(@"URL is  :%@",url);
                label.enabledTextCheckingTypes = NSTextCheckingTypeLink;
            }
        }
    
        label.text =name;
    }