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

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

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

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

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

Разметка отличается от SO, но вот общая идея. Что я хочу, так это текст:

Этот морф был сгенерирован с Face Dancer, Нажмите, чтобы посмотреть в магазине приложений.

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

Этот морф был создан с помощью Face Dancer. Нажмите на http://example.com/facedancer для просмотра в магазине приложений.

Ответ 1

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

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

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

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

Я создал инструмент командной строки OS X, который принимает RTF файл в качестве входных данных и выводит файл с расширением .data, который содержит NSData из NSCoding. Затем я помещаю файл .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), демонстрирующий технику. Вы можете увидеть это здесь:

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

Ответ 2

Используйте NSMutableAttributedString.

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

Edit

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

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

Ответ 3

Я нашел это действительно полезным, но мне нужно было сделать это во NSMutableAttributedString местах, поэтому я обернул свой подход простым расширением NSMutableAttributedString:

Свифт 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
}

Убедитесь, что атрибут NSTextField Behavior установлен как Выбираемый. Xcode NSTextField behavior attribute

Ответ 4

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

Ответ 5

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

Удачи!

Ответ 6

У меня тоже было подобное требование, изначально я использовал 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;
}

Ответ 7

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

Ответ 8

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

Ответ 9

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

fooobar.com/questions/22451/...

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.

Ура!!

Ответ 10

Я написал метод, который добавляет ссылку (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 $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];

Ответ 11

Просто найдите бесплатное решение для UITextView: введите описание изображения здесь

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

Ответ 12

Обновление:

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

  • Как сделать ссылку, где текст, показанный для ссылки с возможностью клика, отличается от фактической ссылки, которая вызывается:
  • Как настроить ссылки, не используя специальный код для установки атрибутов в тексте.

Оказывается, что 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 выставлять это свойство в "Attributes Inspector". Затем вы можете установить это значение из IB без специального кода.

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

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

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

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

демо-проект DatesInSwift на Github

Ответ 13

Быстрая версия:

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

Ответ 14

Мне нужно было использовать чистую 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];
                                          }
                                      }];

Ответ 15

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

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

Ответ 16

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

вот так:

testTextView.editable = false 
testTextView.dataDetectorTypes = .link

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

testTextView.dataDetectorTypes = .all

Ответ 17

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

Ответ 19

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

Результат: введите описание изображения здесь

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

Ответ 20

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.

Ответ 21

На случай, если у вас возникнут проблемы с тем, что @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
  }
}