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


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

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

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

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

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

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

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

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

20 155

20 ответов:

использовать 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-адреса при нажатии. Или вы можете использовать делегат метод, как предложено в комментариях.

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

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

Use 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. Я тогда положил .файл данных в мой проект и добавить пару строк кода, который загружает текст в поле зрения. Код выглядит так (этот проект был в Свифт):

/*
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 атрибут для моего пользовательского класса. Элемент @IBDesignable атрибут сообщает Xcode, что он должен установить запущенную копию вашего пользовательского класса представления в Interface builder, чтобы вы могли видеть ее в графическом отображении иерархии представлений. ()К сожалению, для этого класса,@IBDesignable собственность кажется шелушащейся. Он работал, когда я впервые добавил его, но затем я удалил текстовое содержимое моего текстового представления, и кликабельные ссылки в моем представлении исчезли, и я не смог их вернуть.)

код для моего RTF_UITextView очень удобно. В дополнение к добавлению и 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 не позволяет надежно просматривать стилизованный текст в 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];
                                          }
                                      }];

быстрое дополнение к оригинальному описанию Дункана C в отношении поведения 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 })
    }

отличная библиотека от @AliSoftwareOHAttributedStringAdditions позволяет легко добавлять ссылки в 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