ios - link - nsattributedstring url swift
Cómo hacer un enlace cliqueable en un NSAttributedString para un (20)
Es trivial hacer que los hipervínculos se puedan hacer clic en una UITextView
. Simplemente establece la casilla "detectar enlaces" en la vista en IB, y detecta enlaces http y los convierte en hipervínculos.
Sin embargo, eso todavía significa que lo que el usuario ve es el enlace "en bruto". Los archivos RTF y HTML le permiten configurar una cadena legible por el usuario con un enlace "detrás".
Es fácil instalar texto atribuido en una vista de texto (o un UILabel
o UITextField
, para el caso). Sin embargo, si el texto atribuido incluye un enlace, no se puede hacer clic en él.
¿Hay alguna manera de hacer que el texto legible por el usuario se pueda hacer clic en un UITextView
, UILabel
o UITextField
?
El marcado es diferente en SO, pero aquí está la idea general. Lo que quiero es un texto como este:
Este cambio se generó con Face Dancer , haga clic para ver en la tienda de aplicaciones.
Lo único que puedo conseguir es esto:
Este cambio se generó con Face Dancer, haga clic en http://example.com/facedancer para verlo en la tienda de aplicaciones.
Actualizar:
Hubo 2 partes clave en mi pregunta:
- Cómo hacer un enlace donde el texto que se muestra para el enlace en el que se puede hacer clic es diferente al enlace real que se invoca:
- Cómo configurar los enlaces sin tener que usar código personalizado para establecer los atributos en el texto.
Resulta que iOS 7 agregó la capacidad de cargar texto atribuido desde NSData
.
UITextView
una subclase personalizada de UITextView
que aprovecha el atributo @IBInspectable
y le permite cargar contenidos desde un archivo RTF directamente en IB. Simplemente escriba el nombre del archivo en IB y la clase personalizada hará el resto.
Aquí están los detalles:
En iOS 7, NSAttributedString
ganó el método initWithData:options:documentAttributes:error:
Ese método le permite cargar un NSAttributedString desde un objeto NSData. Primero puede cargar un archivo RTF en NSData, luego use initWithData:options:documentAttributes:error:
para cargar ese NSData en su vista de texto. (Tenga en cuenta que también hay un método initWithFileURL:options:documentAttributes:error:
que cargará una cadena atribuida directamente desde un archivo, pero ese método quedó obsoleto en iOS 9. Es más seguro utilizar el método initWithData:options:documentAttributes:error:
, que no fue desaprobado.
Quería un método que me permitiera instalar enlaces clicables en mis vistas de texto sin tener que crear ningún código específico para los enlaces que estaba usando.
La solución que se me ocurrió fue crear una subclase personalizada de UITextView. RTF_UITextView
y le doy una propiedad RTF_Filename
llamada RTF_Filename
. Agregar el atributo @IBInspectable
a una propiedad hace que Interface Builder @IBInspectable
esa propiedad en "Attributes Inspector". Luego puede establecer ese valor desde IB sin código personalizado.
También agregué un atributo @IBDesignable
a mi clase personalizada. El atributo @IBDesignable
le dice a Xcode que debe instalar una copia en ejecución de su clase de vista personalizada en el constructor de Interfaces para que pueda verlo en la pantalla gráfica de su jerarquía de vistas. () Desafortunadamente, para esta clase, la propiedad @IBDesignable
parece ser escamosa. Funcionó cuando lo agregué por primera vez, pero luego eliminé el contenido de texto sin formato de mi vista de texto y los enlaces clicables en mi opinión se fueron y no pude recuperarlos).
El código para mi RTF_UITextView
es muy simple. Además de agregar el atributo @IBDesignable
y una propiedad RTF_Filename
con el atributo @IBInspectable
, agregué un método didSet()
a la propiedad RTF_Filename
. El método didSet()
se llama cuando cambia el valor de la propiedad RTF_Filename
. El código para el método didSet()
es bastante 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.");
}
}
}
}
}
Tenga en cuenta que si la propiedad @IBDesignable no le permitirá obtener una vista previa de su texto con estilo en el constructor Interfaz, entonces sería mejor establecer el código anterior como una extensión de UITextView en lugar de una subclase personalizada. De esta forma, podría usarlo en cualquier vista de texto sin tener que cambiar la vista de texto a la clase personalizada.
Vea mi otra respuesta si necesita soportar versiones de iOS anteriores a iOS 7.
Puede descargar un proyecto de ejemplo que incluye esta nueva clase de gitHub:
Proyecto de demostración DatesInSwift en Github
Acabo de crear una subclase de UILabel para abordar especialmente estos casos de uso. Puede agregar múltiples enlaces fácilmente y definir diferentes controladores para ellos. También es compatible con resaltar el enlace presionado cuando toca hacia abajo para obtener información táctil. Por favor, consulte https://github.com/null09264/FRHyperLabel .
En su caso, el código puede tener este aspecto:
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];
}];
Captura de pantalla de muestra (el controlador está configurado para mostrar una alerta en lugar de abrir una url en este caso)
El meollo de mi pregunta fue que quería poder crear enlaces clicables en vistas de texto / campos / etiquetas sin tener que escribir código personalizado para manipular el texto y agregar los enlaces. Yo quería que sea impulsado por los datos.
Finalmente descubrí cómo hacerlo. El problema es que IB no respeta los enlaces integrados.
Además, la versión iOS de NSAttributedString
no le permite inicializar una cadena atribuida desde un archivo RTF. La versión OS X de NSAttributedString
tiene un inicializador que toma un archivo RTF como entrada.
NSAttributedString
ajusta al protocolo NSCoding, por lo que puede convertirlo a / desde NSData
Creé una herramienta de línea de comandos de OS X que toma un archivo RTF como entrada y saca un archivo con la extensión .data que contiene NSData de NSCoding. Luego coloco el archivo .data en mi proyecto y agrego un par de líneas de código que carga el texto en la vista. El código se ve así (este proyecto estaba en 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
}
Para las aplicaciones que usan mucho texto formateado, creo una regla de compilación que le dice a Xcode que todos los archivos .rtf en una carpeta dada son fuente y los archivos .data son el resultado. Una vez que hago eso, simplemente agrego los archivos .rtf al directorio designado (o edito los archivos existentes) y el proceso de compilación se da cuenta de que son nuevos / actualizados, ejecuta la herramienta de línea de comandos y copia los archivos en el paquete de la aplicación. Funciona maravillosamente.
Escribí una publicación de blog que vincula a un proyecto de muestra (Swift) que demuestra la técnica. Puedes verlo aqui:
Creación de URL clicables en un UITextField que se abre en su aplicación
Encontré esto realmente útil, pero necesitaba hacerlo en bastantes lugares, así que he ajustado mi enfoque en una extensión simple a NSMutableAttributedString
:
Rápido
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
}
}
Ejemplo de uso:
let attributedString = NSMutableAttributedString(string:"I love !")
let linkWasSet = attributedString.setAsLink("", linkURL: "http://.com")
if linkWasSet {
// adjust more attributedString properties
}
C objetivo
Acabo de cumplir un requisito para hacer lo mismo en un proyecto puro de Objective-C, así que aquí está la categoría 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
Ejemplo de uso:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:"I love !"];
BOOL linkWasSet = [attributedString setAsLink:@"" linkURL:@"http://.com"];
if (linkWasSet) {
// adjust more attributedString properties
}
He escrito un método que agrega un enlace (linkString) a una cadena (fullString) con una determinada 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;
}
Deberías llamarlo así:
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];
La excelente biblioteca de @AliSoftware OHAttributedStringAdditions
hace que sea más fácil agregar enlaces en UILabel
aquí está la documentación: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel
Mejora menor a la solución de ujell: si usa NSURL en lugar de NSString, puede usar cualquier URL (por ejemplo, URL personalizadas)
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;
¡Que te diviertas!
Necesitaba seguir usando un UILabel puro, así llamado esto desde mi reconocedor de tomas (esto se basa en la respuesta de malex aquí: Índice de caracteres en el punto de contacto para 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];
}
}];
Si desea utilizar NSLinkAttributeName en una UITextView, puede considerar el uso de la biblioteca AttributedTextView. Es una subclase UITextView que hace que sea muy fácil de manejar. Para obtener más información, consulte: https://github.com/evermeer/AttributedTextView
Puede hacer que cualquier parte del texto interactúe así (donde textView1 es 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)
Y para manejar hashtags y menciones puedes usar código como este:
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 })
}
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
Una rápida adición a la descripción original de Duncan C con respecto al comportamiento IB. Él escribe: "Es trivial hacer que los hipervínculos se puedan hacer clic en una UITextView. Simplemente establece la casilla de verificación" detectar enlaces "en la vista en IB, y detecta los enlaces http y los convierte en hipervínculos".
Mi experiencia (al menos en xcode 7) es que también debes quitar el clic en el comportamiento "Editable" para que las URL sean detectadas y cliqueables.
Use NSMutableAttributedString .
NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@"Google"];
[str addAttribute: NSLinkAttributeName value: @"http://www.google.com" range: NSMakeRange(0, str.length)];
yourTextView.attributedText = str;
Editar :
Esto no es directamente sobre la cuestión, sino solo para aclarar, UITextField
y UILabel
no admite la apertura de URL. Si desea usar UILabel
con enlaces, puede verificar TTTAttributedLabel .
También debe establecer el valor de dataDetectorTypes
de su UITextView
en UIDataDetectorTypeLink
o UIDataDetectorTypeAll
para abrir las URL cuando se hace clic. O puede usar el método de delegado como se sugiere en los comentarios.
Use UITextView admite enlaces clicables. Crear cadena atribuida usando el siguiente código
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];
A continuación, configure texto UITextView de la siguiente manera
NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor redColor],
NSUnderlineColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};
customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;
Asegúrese de habilitar el comportamiento "seleccionable" de UITextView en XIB.
Utilice UITextView y establezca dataDetectorTypes for Link.
Me gusta esto:
testTextView.editable = false
testTextView.dataDetectorTypes = .link
Si desea detectar el enlace, el número de teléfono, la dirección, etc.
testTextView.dataDetectorTypes = .all
Versión 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
Yo también tenía un requisito similar, inicialmente usé UILabel y luego me di cuenta de que UITextView es mejor. Hice que UITextView se comportara como UILabel al deshabilitar la interacción y el desplazamiento e hice un método de categoría para NSMutableAttributedString
para establecer un enlace al texto igual a lo que había hecho Karl (+1 para eso) esta es mi versión 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];
}
}
puedes usar el siguiente delegado para manejar la acción
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange
{
// do the task
return YES;
}
si quieres una subcadena activa en tu UITextView, puedes usar mi TextView extendido ... es corto y simple. Puede editarlo como lo desee.
código: https://github.com/marekmand/ActiveSubstringTextView
Ejemplo de Swift 3 para detectar acciones en toques de texto atribuidos
https://.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 la misma manera, puedes agregar cualquier acción que desees con el método shouldInteractWith URL
UITextFieldDelegate.
¡¡Aclamaciones!!
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;
PUNTOS CLAVE:
- Asegúrese de habilitar el comportamiento "seleccionable" de UITextView en XIB.
- Asegúrese de desactivar el comportamiento "editable" de UITextView en XIB.