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