Как сделать кликабельную ссылку в NSAttributedString?

тривиально сделать гиперссылки кликабельными в UITextView. Вы просто установите флажок "обнаруживать ссылки" в представлении В IB, и он обнаруживает HTTP-ссылки и превращает их в гиперссылки.

однако это все равно означает, что пользователь видит "сырую" ссылку. RTF-файлы и HTML-файлы позволяют настроить читаемую пользователем строку со ссылкой "позади".

легко установить приписываемый текст в текстовое представление (или UILabel или UITextField, Если на то пошло.) Однако, когда этот приписываемый текст включает ссылку, она не кликабельна.

есть ли способ сделать читаемый пользователем текст кликабельным в UITextView, UILabel или UITextField?

разметка отличается от SO, но вот общая идея. Мне нужен такой текст:

этот морф был создан с Многоликим, Нажмите для просмотра в app store.

единственное, что я могу сделать это:

этот морф был сгенерирован с Face Dancer, нажмите наhttp://example.com/facedancer для просмотра в app store.

20 ответов


использовать NSMutableAttributedString.

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

редактировать:

это не непосредственно о вопросе, а просто для уточнения,UITextField и UILabel не поддерживает открытие URL-адресов. Если вы хотите использовать UILabel со ссылками вы можете проверить TTTAttributedLabel.

также вы должны установить dataDetectorTypes значение вашего UITextView to UIDataDetectorTypeLink или UIDataDetectorTypeAll для открытия URL-адресов при нажатии. Или вы можете использовать метод delegate как предложено в комментариях.


Я нашел это очень полезным, но мне нужно было сделать это в нескольких местах, поэтому я завернул свой подход в простое расширение до 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, так что вот цель-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
}

незначительное улучшение решения 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;

удачи!


Я только что создал подкласс 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];
}];

Пример Окна (обработчик настроен на всплывающее предупреждение вместо открытия url-адреса в этот случай)

facedancer


у меня тоже было подобное требование, сначала я использовал UILabel, а затем я понял, что UITextView лучше. Я заставил UITextView вести себя как UILabel, отключив взаимодействие и прокрутку и сделал метод категории для NSMutableAttributedString чтобы установить ссылку на текст так же, как сделал Карл (+1 для этого), это моя версия obj C

-(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;
}

используйте 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;

убедитесь, что вы включили "выбираемое" поведение UITextView в XIB.


суть моего вопроса заключалась в том, что я хотел иметь возможность создавать интерактивные ссылки в текстовых представлениях/полях/метках без необходимости писать пользовательский код для управления текстом и добавления ссылок. Я хотел, чтобы он управлялся данными.

Я, наконец, понял, как это сделать. Проблема в том, что IB не соблюдает встроенные ссылки.

кроме того, версия iOS NSAttributedString не позволяет инициализировать приписываемую строку из RTF-файла. Версия OS X NSAttributedString тут есть инициализатор, который принимает RTF-файл в качестве входных данных.

NSAttributedString соответствует протоколу NSCoding, поэтому вы можете преобразовать его в / из NSData

Я создал инструмент командной строки OS X, который принимает RTF-файл в качестве входного и выводит файл с расширением .данные, содержащие NSData из NSCoding. Я тогда положил .файл данных в мой проект и добавьте пару строк кода, который загружает текст в представление. Код выглядит так (этот проект был в 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 файлы в данной папке и источник .файлы данных являются выходными. Как только я это сделаю, я просто добавлю .rtf-файлы в назначенный каталог (или редактировать существующие файлы) и процесс сборки выясняет, что они новые/обновлены, запускает средство командной строки и копирует файлы в пакет приложений. Это прекрасно работает.

Я написал сообщение в блоге, что ссылки на образец (Swift) проекта, демонстрирующий технику. Вы можете увидеть его здесь:

создание интерактивных URL-адресов в UITextField, которые открываются в вашем приложении


Swift 3 пример для обнаружения действий на приписываемых текстовых нажатий

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.

Ура!!


Swift 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

Я написал метод, который добавляет ссылку (linkString) в строку (fullString) с определенным url(urlString):

- (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  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];

просто найдите решение без кода для UITextView: enter image description here

включить обнаружение - > параметры ссылок, URL-адрес, а также электронная почта будут обнаружены и кликабельны!


обновление:

в моем вопросе было 2 ключевые части:

  1. как сделать ссылку, где текст, показанный для кликабельной ссылки, отличается от фактической ссылки, которая вызывается:
  2. как настроить ссылки без использования пользовательского кода для установки атрибутов в тексте.

оказывается, в iOS 7 добавлена возможность загрузки приписываемого текста из NSData.

я создал пользовательский подкласс UITextView это использует@IBInspectable атрибут и позволяет загружать содержимое из RTF-файла непосредственно в IB. Вы просто вводите имя файла в IB, а пользовательский класс делает все остальное.

вот подробности:

в iOS 7, NSAttributedString приобрел метод initWithData:options:documentAttributes:error:. Этот метод позволяет загрузить NSAttributedString из объекта NSData. Сначала вы можете загрузить файл RTF в NSData, а затем использовать initWithData:options:documentAttributes:error: чтобы загрузить NSData в текстовое представление. (Обратите внимание, что существует также метод initWithFileURL:options:documentAttributes:error: это загрузит приписываемую строку непосредственно из файла, но этот метод был устаревшим в iOS 9. Безопаснее использовать метод initWithData:options:documentAttributes:error:, который не был устаревшим.

я хотел метод, который позволил бы мне установить кликабельные ссылки в мои текстовые представления без необходимости создавать какой-либо код, специфичный для ссылок, которые я использовал.

решение, которое я придумал, было создать пользовательский подкласс UITextView, который я называю RTF_UITextView С @IBInspectable свойство RTF_Filename. Добавление @IBInspectable атрибут свойства заставляет Interface Builder предоставлять это свойство в " Инспекторе атрибутов."Затем вы можете установить это значение из IB без пользовательского кода.

я также добавил @IBDesignable атрибут моего пользовательского класса. The @IBDesignable атрибут сообщает Xcode, что он должен установить запущенную копию пользовательского класса представления в Interface builder, чтобы вы могли видеть его на графическом дисплее иерархии представлений. ()К сожалению, для этого класса,@IBDesignable собственность, кажется, шелушится. Он работал, когда я впервые добавил его, но затем я удалил текстовое содержимое моего текстового представления, и кликабельные ссылки в моем представлении исчезли, и я не смог их вернуть.)

код для моего RTF_UITextView очень удобно. В дополнение к добавлению и RTF_Filename собственность с @IBInspectable атрибут, я добавил didSet() метод RTF_Filename собственность. The didSet() метод вызывается в любое время значение RTF_Filename изменение свойства. Код 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 не позволит вам надежно просматривать стилизованный текст в Interface builder, то, возможно, лучше установить вышеуказанный код как расширение UITextView, а не пользовательский подкласс. Таким образом, вы можете использовать его в любом текстовом представлении без необходимости изменять текстовое представление на пользовательский класс.

см. мой другой ответ, Если вам нужна поддержка версии iOS до iOS 7.

вы можете скачать пример проекта, который включает в себя этот новый класс из gitHub:

демонстрационный проект DatesInSwift на Github


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

Мне нужно было продолжать использовать чистый UILabel, так называемый это из моего распознавателя крана (это основано на ответе malex здесь: индекс символов в точке касания для 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];
                                          }
                                      }];

быстрое добавление к исходному описанию поведения Duncan C vis-á-vie IB. Он пишет: "тривиально делать гиперссылки кликабельными в UITextView. Вы просто установите флажок "обнаруживать ссылки" в представлении В IB, и он обнаруживает http-ссылки и превращает их в гиперссылки."

мой опыт (по крайней мере, в xcode 7) заключается в том, что вам также нужно отменить "редактируемое" поведение для URL-адресов, которые будут обнаружены и кликабельны.


Если вы хотите использовать NSLinkAttributeName в 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 })
    }

отличная библиотека от @AliSoftware OHAttributedStringAdditions позволяет легко добавлять ссылки в UILabel вот документация: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel


Если вы хотите активную подстроку в вашем UITextView, вы можете использовать мой расширенный TextView... его короткий и простой. Вы можете редактировать его, как хотите.

результат: enter image description here

код: https://github.com/marekmand/ActiveSubstringTextView


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;

КЛЮЧЕВЫЕ МОМЕНТЫ:

  • убедитесь, что вы включили "выбираемое" поведение UITextView в XIB.
  • убедитесь, что вы отключили" редактируемое " поведение UITextView в XIB.

используйте UITextView и установите dataDetectorTypes для ссылки.

такой:

testTextView.editable = false 
testTextView.dataDetectorTypes = .link

Если вы хотите обнаружить ссылку, номер телефона,адрес и т. д..тогда

testTextView.dataDetectorTypes = .all