Как я могу сделать кликабельную ссылку в NSAttributedString?
Сделать гиперссылки кликабельными в UITextView
, Вы просто устанавливаете флажок "обнаруживать ссылки" в представлении в IB, и он обнаруживает ссылки HTTP и превращает их в гиперссылки.
Тем не менее, это все еще означает, что то, что видит пользователь, является "сырой" ссылкой. RTF-файлы и HTML позволяют настраивать читаемую пользователем строку со ссылкой "позади".
Легко установить атрибутивный текст в текстовое представление (или UILabel
или же UITextField
, в этом отношении.) Однако, когда этот приписанный текст включает ссылку, это не кликабельно.
Есть ли способ сделать читаемый текст кликабельным в UITextView
, UILabel
или же UITextField
?
Разметка отличается от SO, но вот общая идея. Что я хочу, так это текст:
Этот морф был сгенерирован с Face Dancer, Нажмите, чтобы посмотреть в магазине приложений.
Единственное, что я могу получить, это:
Этот морф был создан с помощью Face Dancer. Нажмите на http://example.com/facedancer для просмотра в магазине приложений.
23 ответа
Суть моего вопроса заключалась в том, что я хотел иметь возможность создавать кликабельные ссылки в текстовых представлениях / полях / метках без необходимости писать собственный код для манипулирования текстом и добавления ссылок. Я хотел, чтобы это управлялось данными.
Я наконец понял, как это сделать. Проблема в том, что IB не соблюдает встроенные ссылки.
Кроме того, версия для iOS NSAttributedString
не позволяет инициализировать приписанную строку из файла RTF. Версия OS X NSAttributedString
действительно имеет инициализатор, который принимает файл RTF в качестве входных данных.
NSAttributedString
соответствует протоколу NSCoding, поэтому вы можете преобразовать его в / из NSData
Я создал инструмент командной строки 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, которые открываются в вашем приложении
Используйте 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
в UIDataDetectorTypeLink
или же UIDataDetectorTypeAll
открывать URL при нажатии. Или вы можете использовать метод делегата, как предложено в комментариях.
Я нашел это действительно полезным, но мне нужно было сделать это во многих местах, поэтому я завернул свой подход простым расширением 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 stackru!")
let linkWasSet = attributedString.setAsLink("stackru", linkURL: "http://stackru.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 stackru!"];
BOOL linkWasSet = [attributedString setAsLink:@"stackru" linkURL:@"http://stackru.com"];
if (linkWasSet) {
// adjust more attributedString properties
}
Я только что создал подкласс 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)
Незначительное улучшение решения 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;
Повеселись!
Свифт 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
У меня тоже было подобное требование, сначала я использовал 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.
Быстрый ответ - использование UITextView вместо UILabel. Вам необходимо включить Selectable
и отключить Editable
,
Затем отключите индикаторы прокрутки и отскоки.
Мое решение с использованием NSMutableAttributedString
из строки html NSHTMLTextDocumentType
NSString *s = @"<p><a href='https://itunes.apple.com/us/app/xxxx/xxxx?mt=8'>https://itunes.apple.com/us/app/xxxx/xxxx?mt=8</a></p>";
NSMutableAttributedString *text = [[NSMutableAttributedString alloc]
initWithData: [s dataUsingEncoding:NSUnicodeStringEncoding]
options: @{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType }
documentAttributes: nil
error: nil
];
cell.content.attributedText = text;
Пример Swift 3 для обнаружения действий при атрибуции текстовых нажатий
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.
Ура!!
Я написал метод, который добавляет ссылку (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];
Мне нужно было продолжать использовать чистый 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];
}
}];
Используйте UITextView и установите dataDetectorTypes для Link.
как это:
testTextView.editable = false
testTextView.dataDetectorTypes = .link
Если вы хотите определить ссылку, номер телефона, адрес и т. Д. Затем
testTextView.dataDetectorTypes = .all
В случае, если у вас возникают проблемы с тем, что @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
}
}
Обновить:
На мой вопрос было 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
Атрибут свойства заставляет Интерфейсный Разработчик выставить это свойство в "Инспекторе Атрибутов". Затем вы можете установить это значение из IB без специального кода.
Я также добавил @IBDesignable
приписать мой пользовательский класс. @IBDesignable
Атрибут сообщает Xcode, что он должен установить рабочую копию вашего пользовательского класса представления в Интерфейсный конструктор, чтобы вы могли видеть это в графическом отображении вашей иерархии представлений. () К сожалению, для этого класса @IBDesignable
собственность кажется облупленной. Это сработало, когда я впервые добавил его, но затем я удалил текстовое содержимое моего текстового представления, и кликабельные ссылки в моем представлении исчезли, и я не смог их вернуть.)
Код для моего 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
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
В Swift 5.5
Поскольку Swift 5.5, NSAttributedString полностью локализуем и прост в использовании, даже не определяя количество символов.
func attributedStringBasics(important: Bool) {
var buy = AttributedString("Buy a new iPhone!")
buy.font = .body.bold()
var website = AttributedString("Visit Apple")
website.font = .body.italic()
website.link = URL(string: "http://www.apple.com")
var container = AttributeContainer()
if important {
container.foregroundColor = .red
container.underlineColor = .primary
} else {
container.foregroundColor = .primary
}
buy.mergeAttributes(container)
website.mergeAttributes(container)
print(buy)
print(website)
}
Быстрое добавление к оригинальному описанию Дункана С в отношении поведения 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 })
}
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, то вы можете использовать мой расширенный TextView... он короткий и простой. Вы можете редактировать его как хотите.
Отличная библиотека от AliSoftware OHAttributedStringAdditions
позволяет легко добавлять ссылки в UILabel
вот документация: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel