首页 文章

如何在NSAttributedString中创建可单击的链接?

提问于
浏览
163

UITextView 中点击超链接是微不足道的 . 您只需在IB中的视图上设置"detect links"复选框,它就会检测HTTP链接并将其转换为超链接 .

但是,这仍然意味着用户看到的是“原始”链接 . RTF文件和HTML都允许您设置一个用户可读的字符串,其中包含一个“后面”的链接 .

可以很容易地将属性文本安装到文本视图中(或者 UILabelUITextField ) . 但是,当该属性文本包含链接时,它不可单击 .

有没有办法让用户可读的文本在 UITextViewUILabelUITextField 中可点击?

标记在SO上是不同的,但这是一般的想法 . 我想要的是这样的文字:

此变形是使用Face Dancer生成的,单击可在应用商店中查看 .

我唯一能得到的是:

此变形是使用Face Dancer生成的,单击http://example.com/facedancer可在应用商店中查看 .

21 回答

  • 2

    ujell解决方案的小改进:如果您使用NSURL而不是NSString,则可以使用任何URL(例如自定义URL)

    NSURL *URL = [NSURL URLWithString: @"whatsapp://app"];
    NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@"start Whatsapp"];
    [str addAttribute: NSLinkAttributeName value:URL range: NSMakeRange(0, str.length)];
    yourTextField.attributedText = str;
    

    玩得开心!

  • 3

    使用NSMutableAttributedString .

    NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@"Google"];
    [str addAttribute: NSLinkAttributeName value: @"http://www.google.com" range: NSMakeRange(0, str.length)];
    yourTextView.attributedText = str;
    

    Edit

    这不是直接关于这个问题,只是为了澄清, UITextFieldUILabel 不支持打开URL . 如果您想使用链接 UILabel ,可以查看TTTAttributedLabel .

    此外,您应该将 UITextViewdataDetectorTypes 值设置为 UIDataDetectorTypeLinkUIDataDetectorTypeAll 以在单击时打开URL . 或者您可以使用注释中建议的委托方法 .

  • 0

    我发现这非常有用,但我需要在很多地方做到这一点,所以我将我的方法包含在 NSMutableAttributedString 的简单扩展中:

    Swift 3

    extension NSMutableAttributedString {
    
        public func setAsLink(textToFind:String, linkURL:String) -> Bool {
    
            let foundRange = self.mutableString.range(of: textToFind)
            if foundRange.location != NSNotFound {
                self.addAttribute(.link, value: linkURL, range: foundRange)
                return true
            }
            return false
        }
    }
    

    Swift 2

    import Foundation
    
    extension NSMutableAttributedString {
    
       public func setAsLink(textToFind:String, linkURL:String) -> Bool {
    
           let foundRange = self.mutableString.rangeOfString(textToFind)
           if foundRange.location != NSNotFound {
               self.addAttribute(NSLinkAttributeName, value: linkURL, range: foundRange)
               return true
           }
           return false
       }
    }
    

    用法示例:

    let attributedString = NSMutableAttributedString(string:"I love stackoverflow!")
    let linkWasSet = attributedString.setAsLink("stackoverflow", linkURL: "http://stackoverflow.com")
    
    if linkWasSet {
        // adjust more attributedString properties
    }
    

    Objective-C

    我只是要求在纯Objective-C项目中做同样的事情,所以这里是Objective-C类 .

    @interface NSMutableAttributedString (SetAsLinkSupport)
    
    - (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL;
    
    @end
    
    
    @implementation NSMutableAttributedString (SetAsLinkSupport)
    
    - (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL {
    
         NSRange foundRange = [self.mutableString rangeOfString:textToFind];
         if (foundRange.location != NSNotFound) {
             [self addAttribute:NSLinkAttributeName value:linkURL range:foundRange];
             return YES;
         }
         return NO;
    }
    
    @end
    

    用法示例:

    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:"I love stackoverflow!"];
    
    BOOL linkWasSet = [attributedString setAsLink:@"stackoverflow" linkURL:@"http://stackoverflow.com"];
    
    if (linkWasSet) {
        // adjust more attributedString properties
    }
    
  • 137

    我刚刚创建了一个UILabel的子类来专门解决这些用例 . 您可以轻松添加多个链接并为它们定义不同的处理程序 . 当您触摸触摸反馈时,它还支持突出显示按下的链接 . 请参阅https://github.com/null09264/FRHyperLabel .

    在您的情况下,代码可能是这样的:

    FRHyperLabel *label = [FRHyperLabel new];
    
    NSString *string = @"This morph was generated with Face Dancer, Click to view in the app store.";
    NSDictionary *attributes = @{NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]};
    
    label.attributedText = [[NSAttributedString alloc]initWithString:string attributes:attributes];
    
    [label setLinkForSubstring:@"Face Dancer" withLinkHandler:^(FRHyperLabel *label, NSString *substring){
        [[UIApplication sharedApplication] openURL:aURL];
    }];
    

    Sample Screenshot (处理程序设置为弹出警报而不是在这种情况下打开网址)

    facedancer

  • 26

    我也有类似的要求,最初我使用UILabel然后我意识到UITextView更好 . 我通过禁用交互和滚动使UITextView表现得像UILabel,并为 NSMutableAttributedString 创建了一个类别方法,以设置与Karl所做的相同的文本链接(1为此)这是我的对象版本

    -(void)setTextAsLink:(NSString*) textToFind withLinkURL:(NSString*) url
    {
        NSRange range = [self.mutableString rangeOfString:textToFind options:NSCaseInsensitiveSearch];
    
        if (range.location != NSNotFound) {
    
            [self addAttribute:NSLinkAttributeName value:url range:range];
            [self addAttribute:NSForegroundColorAttributeName value:[UIColor URLColor] range:range];
        }
    }
    

    你可以使用下面的委托来处理这个动作

    - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange
    {
        // do the task
        return YES;
    }
    
  • 1

    使用UITextView它支持可点击的链接 . 使用以下代码创建属性字符串

    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];
    

    然后按如下方式设置UITextView文本

    NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor redColor],
    
                                     NSUnderlineColorAttributeName: [UIColor blueColor],
    
                                     NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};
    
    customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
    textView.attributedText = attributedString;
    

    确保在XIB中启用UITextView的“可选”行为 .

  • 11

    我的问题的核心是我希望能够在文本视图/字段/标签中创建可点击链接,而无需编写自定义代码来操作文本和添加链接 . 我希望它是数据驱动的 .

    我终于想出了怎么做 . 问题是IB不尊重嵌入式链接 .

    此外,iOS版本的 NSAttributedString 不允许您从RTF文件初始化属性字符串 . OS X版本的 NSAttributedString does 有一个初始化程序,它将RTF文件作为输入 .

    NSAttributedString 符合NSCoding协议,因此您可以将其转换为NSData或从NSData转换

    我创建了一个OS X命令行工具,它将RTF文件作为输入,并输出一个扩展名为.data的文件,其中包含NSCoding的NSData . 然后我将.data文件放入我的项目中,并添加几行代码,将文本加载到视图中 . 代码看起来像这样(这个项目在Swift中):

    /*
    If we can load a file called "Dates.data" from the bundle and convert it to an attributed string,
    install it in the dates field. The contents contain clickable links with custom URLS to select
    each date.
    */
    if
      let datesPath = NSBundle.mainBundle().pathForResource("Dates", ofType: "data"),
      let datesString = NSKeyedUnarchiver.unarchiveObjectWithFile(datesPath) as? NSAttributedString
    {
      datesField.attributedText = datesString
    }
    

    对于使用大量格式化文本的应用程序,我创建了一个构建规则,告诉Xcode给定文件夹中的所有.rtf文件都是源文件,而.data文件是输出文件 . 一旦我这样做,我只需将.rtf文件添加到指定目录(或编辑现有文件),构建过程就会发现它们是新的/更新的,运行命令行工具,并将文件复制到应用程序包中 . 它工作得很漂亮 .

    我写了一篇博文,链接到展示该技术的示例(Swift)项目 . 你可以看到这里:

    Creating clickable URLs in a UITextField that open in your app

  • 3

    斯威夫特4:

    var string = "Google"
    var attributedString = NSMutableAttributedString(string: string, attributes:[NSAttributedStringKey.link: URL(string: "http://www.google.com")!])
    
    yourTextView.attributedText = attributedString
    

    Swift 3.1:

    var string = "Google"
    var attributedString = NSMutableAttributedString(string: string, attributes:[NSLinkAttributeName: URL(string: "http://www.google.com")!])
    
    yourTextView.attributedText = attributedString
    
  • 27

    Swift 3 example to detect actions on attributed text taps

    https://stackoverflow.com/a/44226491/5516830

    let termsAndConditionsURL = TERMS_CONDITIONS_URL;
    let privacyURL            = PRIVACY_URL;
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        self.txtView.delegate = self
        let str = "By continuing, you accept the Terms of use and Privacy policy"
        let attributedString = NSMutableAttributedString(string: str)
        var foundRange = attributedString.mutableString.range(of: "Terms of use") //mention the parts of the attributed text you want to tap and get an custom action
        attributedString.addAttribute(NSLinkAttributeName, value: termsAndConditionsURL, range: foundRange)
        foundRange = attributedString.mutableString.range(of: "Privacy policy")
        attributedString.addAttribute(NSLinkAttributeName, value: privacyURL, range: foundRange)
        txtView.attributedText = attributedString
    }
    
    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "WebView") as! SKWebViewController
    
        if (URL.absoluteString == termsAndConditionsURL) {
            vc.strWebURL = TERMS_CONDITIONS_URL
            self.navigationController?.pushViewController(vc, animated: true)
        } else if (URL.absoluteString == privacyURL) {
            vc.strWebURL = PRIVACY_URL
            self.navigationController?.pushViewController(vc, animated: true)
        }
        return false
    }
    

    同样明智的你可以用 shouldInteractWith URL UITextFieldDelegate方法添加你想要的任何动作 .

    干杯!!

  • 14

    我编写了一个方法,它将一个链接(linkString)添加到一个带有某个url(urlString)的字符串(fullString):

    - (NSAttributedString *)linkedStringFromFullString:(NSString *)fullString withLinkString:(NSString *)linkString andUrlString:(NSString *)urlString
    {
        NSRange range = [fullString rangeOfString:linkString options:NSLiteralSearch];
        NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:fullString];
    
        NSMutableParagraphStyle *paragraphStyle = NSMutableParagraphStyle.new;
        paragraphStyle.alignment = NSTextAlignmentCenter;
        NSDictionary *attributes = @{NSForegroundColorAttributeName:RGB(0x999999),
                                     NSFontAttributeName:[UIFont fontWithName:@"HelveticaNeue-Light" size:10],
                                     NSParagraphStyleAttributeName:paragraphStyle};
        [str addAttributes:attributes range:NSMakeRange(0, [str length])];
        [str addAttribute: NSLinkAttributeName value:urlString range:range];
    
        return str;
    }
    

    你应该这样称呼它:

    NSString *fullString = @"A man who bought the Google.com domain name for $12 and owned it for about a minute has been rewarded by Google for uncovering the flaw.";
    NSString *linkString = @"Google.com";
    NSString *urlString = @"http://www.google.com";
    
    _youTextView.attributedText = [self linkedStringFromFullString:fullString withLinkString:linkString andUrlString:urlString];
    
  • 3

    只需找到UITextView的无代码解决方案:
    enter image description here

    启用检测 - >链接选项,URL和电子邮件将被检测并可点击!

  • 2

    更新:

    我的问题有两个关键部分:

    • 如何 Build 一个链接,其中为可点击链接显示的文本与调用的实际链接不同:

    • 如何设置链接而无需使用自定义代码在文本上设置属性 .

    事实证明,iOS 7添加了从 NSData 加载属性文本的功能 .

    我创建了一个 UITextView 的自定义子类,它利用 @IBInspectable 属性,允许您直接在IB中加载RTF文件中的内容 . 您只需在IB中输入文件名,其余的就是自定义类 .

    以下是详细信息:

    在iOS 7中, NSAttributedString 获得了方法 initWithData:options:documentAttributes:error: . 该方法允许您从NSData对象加载NSAttributedString . 您可以先将RTF文件加载到NSData中,然后使用 initWithData:options:documentAttributes:error: 将NSData加载到文本视图中 . (请注意,还有一个方法 initWithFileURL:options:documentAttributes:error: 将直接从文件加载属性字符串,但在iOS 9中不推荐使用该方法 . 使用方法 initWithData:options:documentAttributes:error: 更安全,不使用该方法 .

    我想要一种方法,让我可以在我的文本视图中安装可点击链接,而无需创建任何特定于我正在使用的链接的代码 .

    我想出的解决方案是创建一个UITextView的自定义子类,我调用 RTF_UITextView 并给它一个名为 RTF_Filename@IBInspectable 属性 . 将 @IBInspectable 属性添加到属性会导致Interface Builder在_1764357中公开该属性 . 然后,您可以在IB中使用自定义代码设置该值 .

    我还在自定义类中添加了 @IBDesignable 属性 . @IBDesignable 属性告诉Xcode它应该将自定义视图类的运行副本安装到“接口”构建器中,以便您可以在视图层次结构的图形显示中看到它 . ()不幸的是,对于这个类, @IBDesignable 属性似乎是不稳定的 . 它在我第一次添加它时起作用,但后来我删除了文本视图的纯文本内容,我视图中的可点击链接消失了,我无法将它们取回 . )

    RTF_UITextView 的代码非常简单 . 除了使用 @IBInspectable 属性添加 @IBDesignable 属性和 RTF_Filename 属性之外,我还在 RTF_Filename 属性中添加了 didSet() 方法 . 只要 RTF_Filename 属性的值发生更改,就会调用 didSet() 方法 . didSet() 方法的代码非常简单:

    @IBDesignable
    class RTF_UITextView: UITextView
    {
      @IBInspectable
      var RTF_Filename: String?
        {
        didSet(newValue)
        {
          //If the RTF_Filename is nil or the empty string, don't do anything
          if ((RTF_Filename ?? "").isEmpty)
          {
            return
          }
          //Use optional binding to try to get an URL to the
          //specified filename in the app bundle. If that succeeds, try to load
          //NSData from the file.
          if let fileURL = NSBundle.mainBundle().URLForResource(RTF_Filename, withExtension: "rtf"),
    
            //If the fileURL loads, also try to load NSData from the URL.
            let theData = NSData(contentsOfURL: fileURL)
          {
            var aString:NSAttributedString
            do
            {
              //Try to load an NSAttributedString from the data
              try
                aString = NSAttributedString(data: theData,
                  options: [:],
                  documentAttributes:  nil
              )
              //If it succeeds, install the attributed string into the field.
              self.attributedText = aString;
            }
            catch
            {
              print("Nerp.");
            }
          }
    
        }
      }
    }
    

    请注意,如果@IBDesignable属性不能可靠地允许您在“界面”构建器中预览样式化文本,那么最好将上面的代码设置为UITextView的扩展而不是自定义子类 . 这样,您可以在任何文本视图中使用它,而无需将文本视图更改为自定义类 .

    如果您需要支持iOS 7之前的iOS版本,请参阅我的其他答案 .

    您可以从gitHub下载包含此新类的示例项目:

    DatesInSwift demo project在Github上

  • 3

    Swift版本:

    // Attributed String for Label
        let plainText = "Apkia"
        let styledText = NSMutableAttributedString(string: plainText)
        // Set Attribuets for Color, HyperLink and Font Size
        let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(14.0), NSLinkAttributeName:NSURL(string: "http://apkia.com/")!, NSForegroundColorAttributeName: UIColor.blueColor()]
        styledText.setAttributes(attributes, range: NSMakeRange(0, plainText.characters.count))
        registerLabel.attributedText = styledText
    
  • 11

    我需要继续使用纯UILabel,所以从我的tap识别器中调用它(这是基于malex的响应:Character index at touch point for UILabel

    UILabel* label = (UILabel*)gesture.view;
    CGPoint tapLocation = [gesture locationInView:label];
    
    // create attributed string with paragraph style from label
    
    NSMutableAttributedString* attr = [label.attributedText mutableCopy];
    NSMutableParagraphStyle* paragraphStyle = [NSMutableParagraphStyle new];
    paragraphStyle.alignment = label.textAlignment;
    
    [attr addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, label.attributedText.length)];
    
    // init text storage
    
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attr];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];
    
    // init text container
    
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(label.frame.size.width, label.frame.size.height+100) ];
    textContainer.lineFragmentPadding  = 0;
    textContainer.maximumNumberOfLines = label.numberOfLines;
    textContainer.lineBreakMode        = label.lineBreakMode;
    
    [layoutManager addTextContainer:textContainer];
    
    // find tapped character
    
    NSUInteger characterIndex = [layoutManager characterIndexForPoint:tapLocation
                                                      inTextContainer:textContainer
                             fractionOfDistanceBetweenInsertionPoints:NULL];
    
    // process link at tapped character
    
    [attr enumerateAttributesInRange:NSMakeRange(characterIndex, 1)
                                             options:0
                                          usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
                                              if (attrs[NSLinkAttributeName]) {
                                                  NSString* urlString = attrs[NSLinkAttributeName];
                                                  NSURL* url = [NSURL URLWithString:urlString];
                                                  [[UIApplication sharedApplication] openURL:url];
                                              }
                                          }];
    
  • 0

    Duncan C对IB行为的原始描述的快速补充 . 他写道:“在UITextView中点击超链接是微不足道的 . 你只需在IB中的视图上设置”检测链接“复选框,它就会检测到http链接并将它们变成超链接 . ”

    我的经验(至少在xcode 7中)是你还必须取消点击“可编辑”行为,以便检测和点击网址 .

  • 8

    使用UITextView并为Link设置dataDetectorTypes .

    像这样:

    testTextView.editable = false 
    testTextView.dataDetectorTypes = .link
    

    如果你想检测链接,电话号码,地址等等

    testTextView.dataDetectorTypes = .all
    
  • 104

    如果要在UITextView中使用NSLinkAttributeName,则可以考虑使用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 })
        }
    
  • 17

    来自@AliSoftware OHAttributedStringAdditions的优秀图书馆可以轻松地在 UILabel 中添加链接,这里是文档:https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel

  • 0

    如果您想在UITextView中使用活动子字符串,那么您可以使用我的扩展TextView ...简短而简单 . 您可以根据需要进行编辑 .

    结果:
    enter image description here

    代码:https://github.com/marekmand/ActiveSubstringTextView

  • 0
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];
    
    NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor redColor],   
                                     NSUnderlineColorAttributeName: [UIColor blueColor],
                                     NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};
    
    customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
    textView.attributedText = attributedString;
    

    要点:

    • 确保在XIB中启用UITextView的"Selectable"行为 .

    • 确保在XIB中禁用UITextView的"Editable"行为 .

  • 4

    如果您对@Karl Nosworthy和@esilver上面提供的内容有疑问,我已将NSMutableAttributedString扩展更新为其Swift 4版本 .

    extension NSMutableAttributedString {
    
    public func setAsLink(textToFind:String, linkURL:String) -> Bool {
    
        let foundRange = self.mutableString.range(of: textToFind)
        if foundRange.location != NSNotFound {
             _ = NSMutableAttributedString(string: textToFind)
            // Set Attribuets for Color, HyperLink and Font Size
            let attributes = [NSFontAttributeName: UIFont.bodyFont(.regular, shouldResize: true), NSLinkAttributeName:NSURL(string: linkURL)!, NSForegroundColorAttributeName: UIColor.blue]
    
            self.setAttributes(attributes, range: foundRange)
            return true
        }
        return false
      }
    }
    

相关问题