Как я могу сделать кликабельную ссылку в 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, поэтому вы можете преобразовать его в/из NSDatap >
Я создал инструмент командной строки 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 })
}
Ответ 18
Отличная библиотека из @AliSoftware OHAttributedStringAdditions
позволяет легко добавить ссылки в UILabel
вот документация: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel
Ответ 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
}
}