Il est trivial de rendre les hyperliens cliquables dans une UITextView
. Vous venez de définir la case à cocher "détecter les liens" dans la vue dans IB, qui détecte les liens HTTP et les transforme en liens hypertexte.
Cependant, cela signifie toujours que ce que l'utilisateur voit est le lien "brut". Les fichiers RTF et HTML vous permettent tous les deux de configurer une chaîne lisible par l'utilisateur avec un lien "derrière" celle-ci.
Il est facile d'installer du texte attribué dans une vue de texte (ou une variable UILabel
ou UITextField
d'ailleurs). Toutefois, lorsque ce texte attribué inclut un lien, il ne peut pas être cliqué.
Existe-t-il un moyen de rendre le texte lisible par l'utilisateur cliquable dans UITextView
, UILabel
ou UITextField
?
Le balisage est différent sur SO, mais voici l’idée générale. Ce que je veux, c'est un texte comme celui-ci:
Ce morph a été généré avec Face Dancer , Cliquez pour voir dans l'App Store.
La seule chose que je peux obtenir est la suivante:
Ce morph a été généré avec Face Dancer, cliquez sur http://example.com/facedancer pour afficher dans l'App Store.
Le cœur de ma question était que je voulais pouvoir créer des liens cliquables dans des vues/champs/étiquettes de texte sans avoir à écrire du code personnalisé pour manipuler le texte et ajouter les liens. Je voulais que ce soit axé sur les données.
J'ai enfin compris comment le faire. Le problème est que IB n'honore pas les liens incorporés.
De plus, la version iOS de NSAttributedString
ne vous permet pas d’initialiser une chaîne attribuée à partir d’un fichier RTF. La version OS X de NSAttributedString
ne a-t-elle un initialiseur qui prend un fichier RTF en entrée.
NSAttributedString
est conforme au protocole NSCoding, vous pouvez donc le convertir en/à partir de NSData
J'ai créé un outil de ligne de commande OS X qui prend un fichier RTF en entrée et génère un fichier portant l'extension .data contenant le NSData de NSCoding. J'ai ensuite placé le fichier .data dans mon projet et ajouté quelques lignes de code qui chargent le texte dans la vue. Le code ressemble à ceci (ce projet était à 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
}
Pour les applications qui utilisent beaucoup de texte formaté, je crée une règle de construction qui indique à Xcode que tous les fichiers .rtf d'un dossier donné sont source et que les fichiers .data sont la sortie. Une fois que j'ai fait cela, j'ai simplement ajouté des fichiers .rtf au répertoire désigné (ou modifié des fichiers existants) et le processus de construction détermine qu'ils sont nouveaux/mis à jour, exécute l'outil de ligne de commande et copie les fichiers dans l'ensemble d'applications. Cela fonctionne à merveille.
J'ai écrit un article de blog qui renvoie à un exemple de projet (Swift) démontrant la technique. Tu peux le voir ici:
Création d'URL cliquables dans un UITextField qui s'ouvrent dans votre application
Utilisez NSMutableAttributedString .
NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@"Google"];
[str addAttribute: NSLinkAttributeName value: @"http://www.google.com" range: NSMakeRange(0, str.length)];
yourTextView.attributedText = str;
Modifier:
Ceci ne concerne pas directement la question mais juste pour clarifier, UITextField
et UILabel
ne supporte pas l'ouverture d'URL. Si vous voulez utiliser UILabel
avec des liens, vous pouvez vérifier TTTAttributedLabel .
Vous devez également définir la valeur dataDetectorTypes
de votre UITextView
sur UIDataDetectorTypeLink
ou UIDataDetectorTypeAll
pour ouvrir les URL lorsque vous cliquez dessus. Ou vous pouvez utiliser la méthode déléguée comme suggéré dans les commentaires.
J'ai trouvé cela très utile, mais je devais le faire à plusieurs endroits et j'ai donc résumé mon approche dans une simple extension de 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
}
}
Exemple d'utilisation:
let attributedString = NSMutableAttributedString(string:"I love stackoverflow!")
let linkWasSet = attributedString.setAsLink("stackoverflow", linkURL: "http://stackoverflow.com")
if linkWasSet {
// adjust more attributedString properties
}
Objectif c
Je viens juste de répondre à une exigence de faire de même dans un projet purement Objective-C, voici donc la catégorie 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
Exemple d'utilisation:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:"I love stackoverflow!"];
BOOL linkWasSet = [attributedString setAsLink:@"stackoverflow" linkURL:@"http://stackoverflow.com"];
if (linkWasSet) {
// adjust more attributedString properties
}
Assurez-vous que l'attribut Behavior du NSTextField est défini sur Selectable .
Je viens de créer une sous-classe de UILabel pour traiter spécialement de tels cas d'utilisation. Vous pouvez facilement ajouter plusieurs liens et définir différents gestionnaires pour eux. Il prend également en charge la mise en surbrillance du lien pressé lorsque vous appuyez dessus pour obtenir un retour tactile. Veuillez vous référer à https://github.com/null09264/FRHyperLabel .
Dans votre cas, le code peut ressembler à ceci:
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];
}];
Sample Screenshot (le gestionnaire est configuré pour afficher une alerte au lieu d'ouvrir une URL dans ce cas)
Amélioration mineure de la solution ujell: si vous utilisez NSURL au lieu d’une chaîne NSString, vous pouvez utiliser n’importe quelle URL (par exemple, des URL personnalisées)
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;
S'amuser!
Moi aussi, j'avais une exigence similaire, j'ai d'abord utilisé UILabel, puis j'ai réalisé que UITextView est meilleur. J'ai fait que UITextView se comporte comme UILabel en désactivant l'interaction et le défilement et j'ai créé une méthode de catégorie permettant à NSMutableAttributedString
de définir le lien sur un texte identique à ce que Karl avait fait (+1 pour cela). Ceci est ma version 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];
}
}
vous pouvez utiliser le délégué ci-dessous pour gérer l'action
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange
{
// do the task
return YES;
}
Utilisez UITextView s'il prend en charge les liens cliquables . Créez une chaîne attribuée à l'aide du code suivant
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];
Puis définissez le texte UITextView comme suit
NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor redColor],
NSUnderlineColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};
customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;
Assurez-vous que vous activez le comportement "sélectionnable" de UITextView dans XIB.
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
Swift 3 exemple pour détecter des actions sur des taps de texte attribués
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
}
De même, vous pouvez ajouter n’importe quelle action de votre choix avec la méthode shouldInteractWith URL
UITextFieldDelegate.
À votre santé!!
J'ai écrit une méthode qui ajoute un lien (linkString) à une chaîne (fullString) avec une certaine 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;
}
Vous devriez l'appeler comme ça:
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];
Version rapide:
// 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
Ma question comportait 2 éléments clés:
Il s’avère que iOS 7 a ajouté la possibilité de charger du texte attribué à partir de NSData
.
J'ai créé une sous-classe personnalisée de UITextView
qui tire parti de l'attribut @IBInspectable
et vous permet de charger le contenu d'un fichier RTF directement dans IB. Vous tapez simplement le nom de fichier dans IB et la classe personnalisée fait le reste.
Voici les détails:
Dans iOS 7, NSAttributedString
a gagné la méthode initWithData:options:documentAttributes:error:
. Cette méthode vous permet de charger un NSAttributedString à partir d'un objet NSData. Vous pouvez d'abord charger un fichier RTF dans NSData, puis utiliser initWithData:options:documentAttributes:error:
pour charger ce NSData dans votre affichage texte. (Notez qu'il existe également une méthode initWithFileURL:options:documentAttributes:error:
qui chargera une chaîne attribuée directement à partir d'un fichier, mais cette méthode était obsolète dans iOS 9. Il est préférable d'utiliser la méthode initWithData:options:documentAttributes:error:
, qui n'était pas obsolète.
Je recherchais une méthode permettant d’installer des liens cliquables dans mes vues en mode texte sans avoir à créer de code spécifique aux liens que j’utilisais.
La solution que j'ai proposée consistait à créer une sous-classe personnalisée de UITextView I call RTF_UITextView
et à lui attribuer une propriété @IBInspectable
appelée RTF_Filename
. L'ajout de l'attribut @IBInspectable
à une propriété entraîne l'Interface Builder à exposer cette propriété dans "l'Inspecteur des attributs". Vous pouvez ensuite définir cette valeur à partir de IB sans code personnalisé.
J'ai également ajouté un attribut @IBDesignable
à ma classe personnalisée. L'attribut @IBDesignable
indique à Xcode qu'il doit installer une copie active de votre classe de vue personnalisée dans le générateur Interface afin que vous puissiez la voir sur l'affichage graphique de votre hiérarchie de vues. () Malheureusement, pour cette classe, la propriété @IBDesignable
semble être flaky. Cela fonctionnait lorsque je l'ai ajouté pour la première fois, mais j'ai ensuite supprimé le contenu en texte brut de ma vue texte et les liens cliquables dans ma vue ont disparu et je n'ai pas été en mesure de les récupérer.)
Le code de mon RTF_UITextView
est très simple. En plus d'ajouter l'attribut @IBDesignable
et une propriété RTF_Filename
avec l'attribut @IBInspectable
, j'ai ajouté une méthode didSet()
à la propriété RTF_Filename
. La méthode didSet()
est appelée à chaque fois que la valeur de la propriété RTF_Filename
change. Le code de la méthode didSet()
est assez simple:
@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.");
}
}
}
}
}
Notez que si la propriété @IBDesignable ne vous permet pas de prévisualiser votre texte stylé dans le générateur Interface, il peut être préférable de définir le code ci-dessus comme une extension de UITextView plutôt que comme une sous-classe personnalisée. De cette façon, vous pouvez l'utiliser dans n'importe quelle vue texte sans avoir à changer la vue texte en classe personnalisée.
Voir mon autre réponse si vous devez prendre en charge les versions iOS antérieures à iOS 7.
Vous pouvez télécharger un exemple de projet contenant cette nouvelle classe à partir de gitHub:
Projet de démonstration DatesInSwift sur Github
Je devais continuer à utiliser un UILabel pur, appelé ainsi depuis mon outil de reconnaissance de tap (ceci est basé sur la réponse de malex ici: Index de caractères au point de contact pour 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];
}
}];
Un ajout rapide à la description originale de Duncan C concernant le comportement vis-à-vis d'IB. Il écrit: "Il est simple de rendre les liens hypertexte cliquables dans une UITextView. Vous devez simplement cocher la case" détecter les liens "dans la vue dans IB. Elle détecte les liens http et les transforme en liens."
Mon expérience (au moins dans xcode 7) est que vous devez également décocher le comportement "Modifiable" pour que les URL soient détectées et cliquables.
Utilisez UITextView et définissez dataDetectorTypes pour Link.
comme ça:
testTextView.editable = false
testTextView.dataDetectorTypes = .link
Si vous souhaitez détecter un lien, un numéro de téléphone, une adresse, etc.
testTextView.dataDetectorTypes = .all
Si vous souhaitez utiliser le NSLinkAttributeName dans une UITextView, vous pouvez envisager d'utiliser la bibliothèque AttributedTextView. C'est une sous-classe UITextView qui le rend très facile à manipuler. Pour plus d'informations, voir: https://github.com/evermeer/AttributedTextView
Vous pouvez faire interagir n'importe quelle partie du texte comme ceci (où textView1 est un IBoutlet UITextView):
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)
Et pour gérer les hashtags et les mentions, vous pouvez utiliser un code comme celui-ci:
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 })
}
La réponse rapide utilise UITextView au lieu de UILabel. Vous devez activer Selectable
et désactiver Editable
.
Puis désactivez les indicateurs de défilement et les rebonds.
Ma solution utilisant NSMutableAttributedString
à partir de chaîne 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;
L'excellente bibliothèque de @AliSoftwareOHAttributedStringAdditions
facilite l'ajout de liens dans UILabel
voici la documentation: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel
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;
Si vous avez des problèmes avec ce que @Karl Nosworthy et @esilver ont fourni ci-dessus, j'ai mis à jour l'extension NSMutableAttributedString à sa version 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
}
}
si vous voulez une sous-chaîne active dans votre UITextView, vous pouvez utiliser mon extension TextView ... c'est simple et rapide. Vous pouvez l'éditer comme vous le souhaitez.