Tengo un UITextView que muestra una NSAttributedString. Esta cadena contiene palabras que me gustaría hacer para tocar, de tal manera que cuando son intervenidos me llamó de nuevo para que yo pueda realizar una acción. Me doy cuenta de que UITextView puede detectar toques en una dirección URL y volver a llamar a mi delegado, pero estas no son las Url.

A mí me parece que con iOS 7 y el poder de TextKit ahora debería ser posible, sin embargo no puedo encontrar ningún ejemplo y no estoy seguro de por dónde empezar.

Entiendo que ahora es posible crear atributos personalizados en la cadena (aunque yo no lo ha hecho aún), y tal vez estos serán útiles para detectar si una de las palabras mágicas ha sido aprovechada? En cualquier caso, todavía no sé cómo interceptar los que toca y detectar la palabra que el grifo se ha producido.

Nota de que el iOS 6 de compatibilidad es no necesario.

InformationsquelleAutor tarmes | 2013-10-12

10 Comentarios

  1. 114

    Yo sólo quería ayudar a los demás un poco más. Después de Shmidt la respuesta de que es posible hacer exactamente lo que yo había pedido en mi pregunta original.

    1) Crear un atribuye cadena con atributos personalizados aplicados a la clickable palabras. por ejemplo.

    NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a clickable word" attributes:@{ @"myCustomTag" : @(YES) }];
    [paragraph appendAttributedString:attributedString];

    2) Crear un UITextView para mostrar esa cadena, y agregar un UITapGestureRecognizer a ella. Luego de la manija del grifo:

    - (void)textTapped:(UITapGestureRecognizer *)recognizer
    {
        UITextView *textView = (UITextView *)recognizer.view;
    
        //Location of the tap in text-container coordinates
    
        NSLayoutManager *layoutManager = textView.layoutManager;
        CGPoint location = [recognizer locationInView:textView];
        location.x -= textView.textContainerInset.left;
        location.y -= textView.textContainerInset.top;
    
        //Find the character that's been tapped on
    
        NSUInteger characterIndex;
        characterIndex = [layoutManager characterIndexForPoint:location
                                               inTextContainer:textView.textContainer
                      fractionOfDistanceBetweenInsertionPoints:NULL];
    
        if (characterIndex < textView.textStorage.length) {
    
            NSRange range;
            id value = [textView.attributedText attribute:@"myCustomTag" atIndex:characterIndex effectiveRange:&range];
    
            //Handle as required...
    
            NSLog(@"%@, %d, %d", value, range.location, range.length);
    
        }
    }

    Tan fácil cuando se sabe cómo!

    • ¿Cómo se podría solucionar este en IOS 6 ? Puede usted por favor, eche un vistazo a esta pregunta ?stackoverflow.com/questions/19837522/…
    • En realidad characterIndexForPoint:inTextContainer: fractionOfDistanceBetweenInsertionpoints está disponible en iOS 6, así que yo creo que debería funcionar. Háganoslo saber! Ver este proyecto como un ejemplo: github.com/laevandus/NSTextFieldHyperlinks/blob/master/…
    • La documentación dice que solo está disponible en IOS 7 o posterior 🙂
    • Sí, lo siento. Me conseguía confundirse con Mac OS! Este es iOS7 sólo.
    • No parece obra, cuando no haya seleccionable UITextView
    • Extraño, la mía no es seleccionable…
    • Pero esta no es la respuesta a su propia pregunta. Usted lo pidió para tocar las palabras. Acabo de volver de NINGUNA de - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange y hacer allí lo que usted desea hacer. Usted no necesita todo este cálculo de la posición
    • Necesito algunas palabras a tocar. Que es exactamente lo que este código. Y es la respuesta a mi propia pregunta.
    • Por favor, no se trata de una UITextView no es un UILabel que es lo que yo pensaba que era por un tiempo y se topó con un montón de problemas.
    • Para que esto funcione en un UITableViewCell asegúrese de que el UITextView se establece en Selectable como @PaulBrewczynski mencionado
    • Esto no funciona para iOS 9 @todos. Funciona como un encanto en iOS 7, iOS 8, aunque. Toque no funciona para otro atribuido cadena en el mismo campo de texto.
    • esto funcionaba en iOS 9 para mí.
    • ¿Tener el textview en un tableviewcell en iOS 9 causar ningún problema?
    • No estoy seguro, no lo estoy usando en un tableviewcell
    • ¿Cuál es la forma preferida? el uso de la nslinkattribute nombre o comprobar el toque de destino manualmente en este método?
    • No funciona…su mal carácter de índice a veces….

  2. 54

    La detección de los grifos en atribuidas texto con Swift

    A veces para los principiantes es un poco difícil saber cómo hacer las cosas creadas (que fue para mí de todos modos), así que en este ejemplo es un poco más completa.

    Agregar un UITextView a su proyecto.

    La detección de los grifos en atribuidas texto en un UITextView en iOS

    Salida

    Conecte el UITextView a la ViewController con una toma de corriente denominada textView.

    Atributo personalizado

    Vamos a hacer un atributo personalizado haciendo una La extensión de.

    Nota: Este paso es técnicamente opcional, pero si no lo haces tendrás que modificar el código en el siguiente parte para el uso de un atributo estándar como NSAttributedString.Key.foregroundColor. La ventaja de utilizar un atributo personalizado que usted puede definir qué valores desea almacenar en el texto atribuido gama.

    Añadir un nuevo swift archivo con Archivo > Nuevo > Archivo… > iOS > Fuente > Swift Archivo. Se le puede llamar lo que quieras. Estoy llamando a la mina NSAttributedStringKey+CustomAttribute.swift.

    Pegue el código siguiente:

    import Foundation
    
    extension NSAttributedString.Key {
        static let myAttributeName = NSAttributedString.Key(rawValue: "MyCustomAttribute")
    }

    Código

    Reemplace el código en el ViewController.swift con el siguiente. Nota el UIGestureRecognizerDelegate.

    import UIKit
    class ViewController: UIViewController, UIGestureRecognizerDelegate {
    @IBOutlet weak var textView: UITextView!
    override func viewDidLoad() {
    super.viewDidLoad()
    //Create an attributed string
    let myString = NSMutableAttributedString(string: "Swift attributed text")
    //Set an attribute on part of the string
    let myRange = NSRange(location: 0, length: 5) //range of "Swift"
    let myCustomAttribute = [ NSAttributedString.Key.myAttributeName: "some value"]
    myString.addAttributes(myCustomAttribute, range: myRange)
    textView.attributedText = myString
    //Add tap gesture recognizer to Text View
    let tap = UITapGestureRecognizer(target: self, action: #selector(myMethodToHandleTap(_:)))
    tap.delegate = self
    textView.addGestureRecognizer(tap)
    }
    @objc func myMethodToHandleTap(_ sender: UITapGestureRecognizer) {
    let myTextView = sender.view as! UITextView
    let layoutManager = myTextView.layoutManager
    //location of tap in myTextView coordinates and taking the inset into account
    var location = sender.location(in: myTextView)
    location.x -= myTextView.textContainerInset.left;
    location.y -= myTextView.textContainerInset.top;
    //character index at tap location
    let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    //if index is valid then do something.
    if characterIndex < myTextView.textStorage.length {
    //print the character index
    print("character index: \(characterIndex)")
    //print the character at the index
    let myRange = NSRange(location: characterIndex, length: 1)
    let substring = (myTextView.attributedText.string as NSString).substring(with: myRange)
    print("character at index: \(substring)")
    //check if the tap location has a certain attribute
    let attributeName = NSAttributedString.Key.myAttributeName
    let attributeValue = myTextView.attributedText?.attribute(attributeName, at: characterIndex, effectiveRange: nil)
    if let value = attributeValue {
    print("You tapped on \(attributeName.rawValue) and the value is: \(value)")
    }
    }
    }
    }

    La detección de los grifos en atribuidas texto en un UITextView en iOS

    Ahora si pulsa sobre la «w» de «Swift», obtendrá el siguiente resultado:

    character index: 1
    character at index: w
    You tapped on MyCustomAttribute and the value is: some value

    Notas

    • Aquí he utilizado un atributo personalizado, pero podría haber sido fácilmente NSAttributedString.Key.foregroundColor (color del texto) que tiene un valor de UIColor.green.
    • Anteriormente la vista de texto no podría ser editable o seleccionable, pero en mi respuesta actualizada para Swift 4.2 parece estar funcionando bien sin importar si son o no seleccionado.

    Más estudio

    Esta respuesta se basa en varias otras respuestas a esta pregunta. Además de estos, véase también

    • uso myTextView.textStorage en lugar de myTextView.attributedText.string
    • La detección de toque a través de tap gesto en iOS 9 no funciona para las sucesivas derivaciones. Las actualizaciones sobre eso?
    • Hice este ejemplo de uso de iOS 9 y sucesivas derivaciones trabajado para mí, así que no sé qué decirte. Usted puede tratar de abrir su propia pregunta con más detalles. Si usted recibe una respuesta (o que ya han encontrado una respuesta), deja un comentario aquí para que otros puedan beneficiarse.
    • Estoy usando objective c con un enfoque similar. Y, sucesivas derivaciones para diferentes atribuido objetos no funciona para mí. Un teclado que se muestra cuando se pulsa sobre un atribuye cadena, pero cuando toca en otro atribuido cadena con un teclado que se muestra, el segundo atribuido cadena de no llamar a la girada gesto de reconocimiento de método.
    • Así que en iOS 9 he revisado las aprovechado gesto, funciona sin el teclado, pero cuando tengo el teclado muestra, el grifo no sea reconocido por los clics sucesivos.
    • Encontraste alguna solución para iOS 9? También soy de las que se enfrenta el mismo problema cuando el teclado está mostrando el grifo no ser reconocido. Pero su trabajo bien en iOS 8.
    • Empecé a una nueva pregunta para este problema. Usted puede estrella y comprobación posterior de las respuestas. Siéntase libre de editar la pregunta o añadir comentarios si no hay nada más detalles pertinentes.
    • Hay una nueva respuesta a la pregunta que le hice para usted. Si esto es una solución a su problema, por favor deje un comentario y me lo marca como resuelto.
    • Me estoy poniendo de los grifos para cada personaje. Pero desde el final de la última palabra hasta el final de la textview, me estoy haciendo la última letra. ¿Cómo puedo evitarlo ? @Suragch
    • esta es la detección de sólo la primera . quiero detectar en el medio .
    • Necesito más de 1 línea y que causa algunos problemas en el texto de la detección. Creo que es la misma que menciona @PoolHallJunkie .Todas las ideas para la comprobación de la cadena de altura demasiado?
    • Puedo resolver el problema mediante la adición de cada momento a otro «» cadena vacía al final de mi TextView. De esa manera la detección se detiene después de su última palabra. Espero que ayude
    • El uso de la palabra «mi» en los nombres de método, dice acerca de la «calidad» del código.
    • Yo no recomiendo el uso de «mi» en el código de producción, pero yo lo uso en la enseñanza de ejemplos para que quede muy claro que no es cualquier tipo de variable de sistema pero que hemos creado nosotros mismos. (En el pasado, cuando el aprendizaje de un nuevo concepto he tenido problemas para hacer esa distinción con otras personas de código.)
    • Esto es ridículo. Creo que let <varname> indica claramente que el var se define en el código. my prefijo sólo hace que sea más difícil para leer el código. Es una de las indicaciones del deber de código.
    • también la solución no es completa. No funciona sin una solución que permite a todos gesto reconocedores de proceso.
    • En este trabajo con un UILabel?
    • Esta respuesta ha sido actualizado.
    • He probado un UILabel con mi respuesta actualizada, pero no funcionó.
    • Sí exactamente! No trabajó también para mí!
    • Funciona a la perfección con varios grifos, acabo de poner en un corto rutina para probar esto: si characterIndex < 12 { textView.textColor = UIColor.magenta }else{ textView.textColor = UIColor.blue } Realmente claro & código simple

  3. 32

    Esta es una versión ligeramente modificada, partiendo de @tarmes respuesta. No pude conseguir el valuevariable a devolver nada, pero null sin el tweak a continuación. Además, necesitaba el pleno atributo diccionario regresó con el fin de determinar la acción resultante. Yo habría puesto a ello en los comentarios, pero no parecen tener el rep para hacerlo. Disculpas de antemano si he violado el protocolo.

    Específicos tweak es el uso de textView.textStorage en lugar de textView.attributedText. Como todavía aprendizaje iOS programador, no estoy realmente seguro de por qué es, pero tal vez alguien puede iluminarnos.

    Modificación específica en el toque método de manipulación:

        NSDictionary *attributesOfTappedText = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];

    Código completo en mi punto de vista controlador

    - (void)viewDidLoad
    {
    [super viewDidLoad];
    self.textView.attributedText = [self attributedTextViewString];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];
    [self.textView addGestureRecognizer:tap];
    }  
    - (NSAttributedString *)attributedTextViewString
    {
    NSMutableAttributedString *paragraph = [[NSMutableAttributedString alloc] initWithString:@"This is a string with " attributes:@{NSForegroundColorAttributeName:[UIColor blueColor]}];
    NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a tappable string"
    attributes:@{@"tappable":@(YES),
    @"networkCallRequired": @(YES),
    @"loadCatPicture": @(NO)}];
    NSAttributedString* anotherAttributedString = [[NSAttributedString alloc] initWithString:@" and another tappable string"
    attributes:@{@"tappable":@(YES),
    @"networkCallRequired": @(NO),
    @"loadCatPicture": @(YES)}];
    [paragraph appendAttributedString:attributedString];
    [paragraph appendAttributedString:anotherAttributedString];
    return [paragraph copy];
    }
    - (void)textTapped:(UITapGestureRecognizer *)recognizer
    {
    UITextView *textView = (UITextView *)recognizer.view;
    //Location of the tap in text-container coordinates
    NSLayoutManager *layoutManager = textView.layoutManager;
    CGPoint location = [recognizer locationInView:textView];
    location.x -= textView.textContainerInset.left;
    location.y -= textView.textContainerInset.top;
    NSLog(@"location: %@", NSStringFromCGPoint(location));
    //Find the character that's been tapped on
    NSUInteger characterIndex;
    characterIndex = [layoutManager characterIndexForPoint:location
    inTextContainer:textView.textContainer
    fractionOfDistanceBetweenInsertionPoints:NULL];
    if (characterIndex < textView.textStorage.length) {
    NSRange range;
    NSDictionary *attributes = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];
    NSLog(@"%@, %@", attributes, NSStringFromRange(range));
    //Based on the attributes, do something
    ///if ([attributes objectForKey:...)] //make a network call, load a cat Pic, etc
    }
    }
    • Tenía el mismo problema con el textView.attributedText! GRACIAS por el textView.textStorage sugerencia!
    • La detección de toque a través de tap gesto en iOS 9 no funciona para las sucesivas derivaciones.
  4. 24

    Haciendo de enlace personalizado y haciendo lo que quiere el grifo se ha convertido en mucho más fácil con iOS 7.
    Hay un muy buen ejemplo en Ray Wenderlich

    • Esta es una solución más limpia que la de intentar calcular cadena de posiciones con respecto a su contenedor de vista.
    • El problema es textView debe ser seleccionable, y no quiero que este comportamiento.
    • para el puntero sobre el por qué de mi UITextView no fue la detección de enlaces, incluso cuando yo había puesto para detectar a ellos a través de IB. (Yo también había hecho lo unselectable)
  5. 11

    La WWDC 2013 ejemplo:

    NSLayoutManager *layoutManager = textView.layoutManager;
    CGPoint location = [touch locationInView:textView];
    NSUInteger characterIndex;
    characterIndex = [layoutManager characterIndexForPoint:location
    inTextContainer:textView.textContainer
    fractionOfDistanceBetweenInsertionPoints:NULL];
    if (characterIndex < textView.textStorage.length) { 
    //valid index
    //Find the word range here
    //using -enumerateSubstringsInRange:options:usingBlock:
    }
    • Gracias! Voy a ver la WWDC de vídeo también.
    • Diseños de texto y Efectos con Texto Kit».
  6. 10

    He podido resolver esta bastante simplemente con NSLinkAttributeName

    Swift 2

    class MyClass: UIViewController, UITextViewDelegate {
    @IBOutlet weak var tvBottom: UITextView!
    override func viewDidLoad() {
    super.viewDidLoad()
    let attributedString = NSMutableAttributedString(string: "click me ok?")
    attributedString.addAttribute(NSLinkAttributeName, value: "cs://moreinfo", range: NSMakeRange(0, 5))
    tvBottom.attributedText = attributedString
    tvBottom.delegate = self
    }
    func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
    UtilityFunctions.alert("clicked", message: "clicked")
    return false
    }
    }
    • Usted debe verificar que su URL fue aprovechado y no a otra URL con if URL.scheme == "cs" y return true fuera de la if declaración, por lo que el UITextView puede manejar normal https:// enlaces que son abiertos
    • Lo hice y funcionó razonablemente bien en el iPhone 6 y 6+, pero no funciona en todos los iPhone 5. Fui con Suragch solución anterior, que simplemente funciona. Nunca supe por qué el iPhone 5 tendría un problema con esto, no tenía ningún sentido.
  7. 7

    Ejemplo completo para detectar acciones atribuidas texto con Swift 3

    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
    }

    Y, a continuación, usted puede capturar la acción con shouldInteractWith URL UITextViewDelegate método de delegado.Así que asegúrese de que ha establecido el delegado correctamente.

    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
    }

    Como sabio puede realizar cualquier acción según su requisito.

    Saludos!!

  8. 1

    Esto puede funcionar bien con un enlace corto, multilink en un textview. Que funcione bien con iOS 6,7,8.

    - (void)tappedTextView:(UITapGestureRecognizer *)tapGesture {
    if (tapGesture.state != UIGestureRecognizerStateEnded) {
    return;
    }
    UITextView *textView = (UITextView *)tapGesture.view;
    CGPoint tapLocation = [tapGesture locationInView:textView];
    NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber
    error:nil];
    NSArray* resultString = [detector matchesInString:self.txtMessage.text options:NSMatchingReportProgress range:NSMakeRange(0, [self.txtMessage.text length])];
    BOOL isContainLink = resultString.count > 0;
    if (isContainLink) {
    for (NSTextCheckingResult* result in  resultString) {
    CGRect linkPosition = [self frameOfTextRange:result.range inTextView:self.txtMessage];
    if(CGRectContainsPoint(linkPosition, tapLocation) == 1){
    if (result.resultType == NSTextCheckingTypePhoneNumber) {
    NSString *phoneNumber = [@"telprompt://" stringByAppendingString:result.phoneNumber];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]];
    }
    else if (result.resultType == NSTextCheckingTypeLink) {
    [[UIApplication sharedApplication] openURL:result.URL];
    }
    }
    }
    }
    }
    - (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView
    {
    UITextPosition *beginning = textView.beginningOfDocument;
    UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
    UITextPosition *end = [textView positionFromPosition:start offset:range.length];
    UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
    CGRect firstRect = [textView firstRectForRange:textRange];
    CGRect newRect = [textView convertRect:firstRect fromView:textView.textInputView];
    return newRect;
    }
    • La detección de toque a través de tap gesto en iOS 9 no funciona para las sucesivas derivaciones.
  9. 1

    Con Swift 5 y iOS 12, se puede crear una subclase de UITextView y reemplazar punto(en el interior:con:) con algunos TextKit implementación con el fin de hacer sólo algunos NSAttributedStrings en ella para tocar.


    El código siguiente muestra cómo crear una UITextView que sólo reacciona a los toques en subrayado NSAttributedStrings en ella:

    InteractiveUnderlinedTextView.swift

    import UIKit
    class InteractiveUnderlinedTextView: UITextView {
    override init(frame: CGRect, textContainer: NSTextContainer?) {
    super.init(frame: frame, textContainer: textContainer)
    configure()
    }
    required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    configure()
    }
    func configure() {
    isScrollEnabled = false
    isEditable = false
    isSelectable = false
    isUserInteractionEnabled = true
    }
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    let superBool = super.point(inside: point, with: event)
    let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    guard characterIndex < textStorage.length else { return false }
    let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil)
    return superBool && attributes[NSAttributedString.Key.underlineStyle] != nil
    }
    }

    ViewController.swift

    import UIKit
    class ViewController: UIViewController {
    override func viewDidLoad() {
    super.viewDidLoad()
    let linkTextView = InteractiveUnderlinedTextView()
    linkTextView.backgroundColor = .orange
    let mutableAttributedString = NSMutableAttributedString(string: "Some text\n\n")
    let attributes = [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue]
    let underlinedAttributedString = NSAttributedString(string: "Some other text", attributes: attributes)
    mutableAttributedString.append(underlinedAttributedString)
    linkTextView.attributedText = mutableAttributedString
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(underlinedTextTapped))
    linkTextView.addGestureRecognizer(tapGesture)
    view.addSubview(linkTextView)
    linkTextView.translatesAutoresizingMaskIntoConstraints = false
    linkTextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    linkTextView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    linkTextView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor).isActive = true
    }
    @objc func underlinedTextTapped(_ sender: UITapGestureRecognizer) {
    print("Hello")
    }
    }

Dejar respuesta

Please enter your comment!
Please enter your name here