Come posso creare un link cliccabile in una NSAttributedString?

È banale rendere i collegamenti ipertestuali selezionabili in un UITextView . È sufficiente impostare la casella di controllo “Rileva collegamenti” nella vista in IB e rileva i collegamenti HTTP e li trasforma in collegamenti ipertestuali.

Tuttavia, ciò significa che ciò che l’utente vede è il link “raw”. I file RTF e HTML consentono di impostare una stringa leggibile dall’utente con un collegamento “dietro”.

È facile installare il testo attribuito in una visualizzazione di testo (o UILabel o UITextField , per quella materia). Tuttavia, quando il testo attribuito include un collegamento, non è selezionabile.

C’è un modo per rendere cliccabile il testo leggibile dall’utente in UITextView , UILabel o UITextField ?

Il markup è diverso su SO, ma qui è l’idea generale. Quello che voglio è un testo come questo:

Questo morph è stato generato con Face Dancer , Fai clic per visualizzare nell’app store.

L’unica cosa che posso ottenere è questa:

Questo morph è stato generato con Face Dancer, fai clic su http://example.com/facedancer per visualizzare nell’app store.

Usa NSMutableAttributedString .

 NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@"Google"]; [str addAttribute: NSLinkAttributeName value: @"http://www.google.com" range: NSMakeRange(0, str.length)]; yourTextView.attributedText = str; 

Modifica :

Questo non riguarda direttamente la domanda, ma solo per chiarire, UITextField e UILabel non supportano l’apertura di URL. Se si desidera utilizzare UILabel con collegamenti, è ansible verificare TTTAttribuitoLabel .

Inoltre, è necessario impostare il valore dataDetectorTypes di UITextView su UIDataDetectorTypeLink o UIDataDetectorTypeAll per aprire gli URL quando si fa clic. Oppure puoi utilizzare il metodo delegate come suggerito nei commenti.

Ho trovato questo veramente utile, ma avevo bisogno di farlo in alcuni punti, quindi ho avvolto il mio approccio in una semplice estensione a 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 } } 

Esempio di utilizzo:

 let attributedString = NSMutableAttributedString(string:"I love stackoverflow!") let linkWasSet = attributedString.setAsLink("stackoverflow", linkURL: "http://stackoverflow.com") if linkWasSet { // adjust more attributedString properties } 

Objective-C

Ho appena raggiunto l’obbligo di fare lo stesso in un puro progetto Objective-C, quindi ecco la categoria 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 

Esempio di utilizzo:

 NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:"I love stackoverflow!"]; BOOL linkWasSet = [attributedString setAsLink:@"stackoverflow" linkURL:@"http://stackoverflow.com"]; if (linkWasSet) { // adjust more attributedString properties } 

Piccolo miglioramento della soluzione di ujell: se usi NSURL invece di NSString, puoi utilizzare qualsiasi URL (ad es. URL personalizzati)

 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; 

Divertiti!

Ho appena creato una sottoclass di UILabel per affrontare specificamente questi casi d’uso. È ansible aggiungere più collegamenti facilmente e definire diversi gestori per loro. Supporta anche l’evidenziazione del collegamento premuto quando si tocca per il feedback del touch. Si prega di fare riferimento a https://github.com/null09264/FRHyperLabel .

Nel tuo caso, il codice può piacere:

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

Schermata di esempio (il gestore è impostato per fare un avviso invece di aprire un url in questo caso)

facedancer

Anch’io ho avuto un requisito simile, inizialmente ho usato UILabel e poi ho capito che UITextView è migliore. Ho fatto in modo che UITextView si comportasse come UILabel disabilitando l’interazione e lo scrolling e creato un metodo di categoria per NSMutableAttributedString per impostare il link al testo uguale a quello che Karl aveva fatto (+1 per quello) questa è la mia versione 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]; } } 

è ansible utilizzare il delegato di seguito per gestire l’azione

 - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange { // do the task return YES; } 

Usa UITextView supporta collegamenti cliccabili. Creare una stringa attribuita utilizzando il seguente codice

 NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks]; 

Quindi imposta il testo UITextView come segue

 NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor redColor], NSUnderlineColorAttributeName: [UIColor blueColor], NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)}; customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links textView.attributedText = attributedString; 

Assicurarsi di abilitare il comportamento “selezionabile” di UITextView in XIB.

Il cuore della mia domanda era che volevo essere in grado di creare collegamenti cliccabili in viste / campi / etichette di testo senza dover scrivere codice personalizzato per manipolare il testo e aggiungere i collegamenti. Volevo che fosse basato sui dati.

Finalmente ho capito come farlo. Il problema è che IB non onora i link incorporati.

Inoltre, la versione iOS di NSAttributedString non consente di inizializzare una stringa attribuita da un file RTF. La versione OS X di NSAttributedString ha un inizializzatore che accetta un file RTF come input.

NSAttributedString conforms al protocollo NSCoding, quindi è ansible convertirlo in / da NSData

Ho creato uno strumento da riga di comando OS X che accetta un file RTF come input e genera un file con estensione .data che contiene NSData da NSCoding. Quindi inserisco il file .data nel mio progetto e aggiungo un paio di righe di codice che caricano il testo nella vista. Il codice è simile a questo (questo progetto era in 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 } 

Per le app che utilizzano molto testo formattato, creo una regola di build che indica a Xcode che tutti i file .rtf in una determinata cartella sono di origine ei file .data sono l’output. Una volta eseguita questa operazione, aggiungo semplicemente i file .rtf alla directory designata (o modifica i file esistenti) e il processo di compilazione rileva che sono nuovi / aggiornati, esegue lo strumento da riga di comando e copia i file nel pacchetto dell’app. Funziona magnificamente.

Ho scritto un post sul blog che si collega a un progetto campione (Swift) che dimostra la tecnica. Potete vederlo qui:

Creazione di URL selezionabili in un campo UIText che si apre nella tua app

Esempio di Swift 3 per rilevare azioni sui tocchi di testo attribuiti

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 } 

Come saggio puoi aggiungere qualsiasi azione tu voglia con shouldInteractWith URL UITextFieldDelegate metodo.

Saluti!!

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 

Ho scritto un metodo, che aggiunge un collegamento (linkString) a una stringa (fullString) con un certo 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; } 

Dovresti chiamarlo così:

 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]; 

Basta trovare una soluzione senza codice per UITextView: inserisci la descrizione dell'immagine qui

Abilita rilevamento-> Opzioni di collegamenti, l’URL e anche l’e-mail saranno rilevati e cliccabili!

Aggiornare:

C’erano 2 parti chiave alla mia domanda:

  1. Come creare un collegamento in cui il testo mostrato per il link cliccabile è diverso dal link effettivo che è stato richiamato:
  2. Come impostare i collegamenti senza dover utilizzare il codice personalizzato per impostare gli attributi sul testo.

Si scopre che iOS 7 ha aggiunto la possibilità di caricare il testo attribuito da NSData .

Ho creato una sottoclass personalizzata di UITextView che sfrutta l’attributo @IBInspectable e consente di caricare i contenuti da un file RTF direttamente in IB. Basta digitare il nome del file in IB e la class personalizzata fa il resto.

Ecco i dettagli:

In iOS 7, NSAttributedString ottenuto il metodo initWithData:options:documentAttributes:error: Quel metodo ti consente di caricare una NSAttributedString da un object NSData. È ansible caricare prima un file RTF in NSData, quindi utilizzare initWithData:options:documentAttributes:error: per caricare quel NSData nella vista del testo. (Si noti che esiste anche un metodo initWithFileURL:options:documentAttributes:error: che caricherà una stringa attribuita direttamente da un file, ma tale metodo è stato deprecato in iOS 9. È più sicuro utilizzare il metodo initWithData:options:documentAttributes:error: , che non è stato deprecato.

Volevo un metodo che permettesse di installare collegamenti selezionabili nelle mie visualizzazioni di testo senza dover creare codice specifico per i collegamenti che stavo utilizzando.

La soluzione che ho trovato è stata quella di creare una sottoclass personalizzata di UITextView. Richiamo RTF_UITextView e dargli una proprietà RTF_Filename chiamata RTF_Filename . Aggiungendo l’attributo @IBInspectable a una proprietà, Interface Builder espone tale proprietà in “Impostazioni Ispettori”. È quindi ansible impostare tale valore da IB senza codice personalizzato.

Ho anche aggiunto un attributo @IBDesignable alla mia class personalizzata. L’attributo @IBDesignable dice a Xcode che dovrebbe installare una copia in esecuzione della class di visualizzazione personalizzata nel generatore di interfacce in modo che tu possa vederlo nella visualizzazione grafica della gerarchia della vista. () Sfortunatamente, per questa class, la proprietà @IBDesignable sembra essere @IBDesignable . Ha funzionato quando l’ho aggiunto per la prima volta, ma poi ho cancellato il contenuto del testo in chiaro della mia visualizzazione testuale e i link cliccabili nella mia vista sono andati via e non sono stato in grado di recuperarli.)

Il codice per il mio RTF_UITextView è molto semplice. Oltre ad aggiungere l’attributo @IBDesignable e una proprietà RTF_Filename con l’attributo @IBInspectable , ho aggiunto un metodo didSet() alla proprietà RTF_Filename . Il metodo didSet() viene chiamato ogni volta che cambia il valore della proprietà RTF_Filename . Il codice per il metodo didSet() è abbastanza semplice:

 @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."); } } } } } 

Nota che se la proprietà @IBDesignable non ti consente in modo affidabile di visualizzare l’anteprima del tuo testo in stile in Interface builder, potrebbe essere meglio impostare il codice sopra come un’estensione di UITextView piuttosto che una sottoclass personalizzata. In questo modo è ansible utilizzarlo in qualsiasi vista testo senza dover modificare la visualizzazione del testo nella class personalizzata.

Vedi la mia altra risposta se devi supportare le versioni di iOS precedenti a iOS 7.

Puoi scaricare un progetto di esempio che include questa nuova class da gitHub:

Progetto demo DatesInSwift su Github

Versione 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 

Avevo bisogno di continuare a usare un UILabel puro, così chiamato dal mio tap riconoscitore (questo è basato sulla risposta di malex qui: Indice dei caratteri al punto di contatto per 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 * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { if (attrs[NSLinkAttributeName]) { NSString* urlString = attrs[NSLinkAttributeName]; NSURL* url = [NSURL URLWithString:urlString]; [[UIApplication sharedApplication] openURL:url]; } }]; 

Un’aggiunta rapida alla descrizione originale di Duncan C vis-à-vie con il comportamento di IB. Scrive: “È banale rendere i collegamenti ipertestuali selezionabili in un UITextView: basta impostare la casella di controllo” Rileva collegamenti “nella vista in IB e rileva i collegamenti http e li trasforma in collegamenti ipertestuali.”

La mia esperienza (almeno in xcode 7) è che devi anche sganciare il comportamento “Modificabile” affinché gli URL vengano rilevati e cliccabili.

Se si desidera utilizzare NSLinkAttributeName in un UITextView, è ansible prendere in considerazione l’utilizzo della libreria AttributedTextView. È una sottoclass UITextView che rende molto facile gestirli. Per maggiori informazioni consultare: https://github.com/evermeer/AttributedTextView

Puoi far interagire qualsiasi parte del testo in questo modo (dove textView1 è un 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) 

E per gestire hashtag e menzioni puoi usare un codice come questo:

 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 }) } 

L’eccellente libreria di @AliSoftware OHAttributedStringAdditions semplifica l’aggiunta di link in UILabel Ecco la documentazione: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel

se vuoi la sottostringa triggers nel tuo UITextView allora puoi usare il mio TextView esteso … è breve e semplice. Puoi modificarlo come vuoi.

risultato: inserisci la descrizione dell'immagine qui

codice: 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; 

PUNTI CHIAVE:

  • Assicurarsi di abilitare il comportamento “selezionabile” di UITextView in XIB.
  • Assicurati di disabilitare il comportamento “Modificabile” di UITextView in XIB.

Usa UITextView e imposta dataDetectorTypes per Link.

come questo:

 testTextView.editable = false testTextView.dataDetectorTypes = .link 

Se si desidera rilevare il collegamento, il numero di telefono, l’indirizzo ecc

 testTextView.dataDetectorTypes = .all