Saltar al contenido

Detectar toques en texto atribuido en un UITextView en iOS

Este grupo redactor ha estado mucho tiempo buscando respuestas a tu interrogante, te ofrecemos la respuestas y deseamos serte de mucha ayuda.

Solución:

Solo quería ayudar a los demás un poco más. Siguiendo la respuesta de Shmidt, es posible hacer exactamente lo que había pedido en mi pregunta original.

1) Crea un atributo string con costumbre attributes aplicado a las palabras en las que se puede hacer clic. p.ej.

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

2) Cree un UITextView para mostrar que stringy agregue un UITapGestureRecognizer. Luego maneja el 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 sabes cómo!

Detectar toques en texto atribuido con Swift

A veces, para los principiantes es un poco difícil saber cómo configurar las cosas (de todos modos lo fue para mí), por lo que este ejemplo es un poco más completo.

Agrega un UITextView a su proyecto.

Toma de corriente

Conecta el UITextView al ViewController con una salida llamada textView.

Personalizado attribute

Vamos a hacer una costumbre attribute haciendo una extensión.

Nota: Este paso es técnicamente opcional, pero si no lo hace, deberá editar el código en la siguiente parte para usar un estándar attribute igual que NSAttributedString.Key.foregroundColor. La ventaja de utilizar un personalizado attribute es que puede definir qué valores desea almacenar en el rango de texto atribuido.

Agregue un nuevo archivo Swift con Archivo> Nuevo> Archivo ...> iOS> Fuente> Archivo Swift. Puedes llamarlo como quieras. Estoy llamando mio NSAttributedStringKey + CustomAttribute.swift.

Pega el siguiente código:

import Foundation

extension NSAttributedString.Key 
    static let myAttributeName = NSAttributedString.Key(rawValue: "MyCustomAttribute")

Código

Reemplace el código en ViewController.swift con lo siguiente. Nota la 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)")
            

        
    

ingrese la descripción de la imagen aquí

Ahora, si toca la "w" de "Swift", debería obtener el siguiente resultado:

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

Notas

  • Aquí usé una costumbre attribute, pero podría haber sido tan fácil NSAttributedString.Key.foregroundColor (color del texto) que tiene un valor de UIColor.green.
  • Anteriormente, la vista de texto no podía ser editable o seleccionable, pero en mi respuesta actualizada para Swift 4.2 parece estar funcionando bien sin importar si estos están seleccionados o no.

Estudio adicional

Esta respuesta se basó en varias otras respuestas a esta pregunta. Además de estos, ver también

  • Efectos y diseños de texto avanzados con el kit de texto (video de la WWDC 2013)
  • Guía de programación de cadenas atribuidas
  • ¿Cómo hago un atribuido string usando Swift?

Esta es una versión ligeramente modificada, basada en la respuesta de @tarmes. No pude conseguir el valuevariable para devolver cualquier cosa menos null sin el ajuste a continuación. Adems, necesitaba el attribute diccionario devuelto para determinar la acción resultante. Hubiera puesto esto en los comentarios pero no parece tener el representante para hacerlo. Disculpas de antemano si he violado el protocolo.

Un ajuste específico es usar textView.textStorage en lugar de textView.attributedText. Como programador de iOS que todavía está aprendiendo, no estoy muy seguro de por qué es así, pero tal vez alguien más pueda aclararnos.

Modificación específica en el método de manejo del grifo:

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

Código completo en mi controlador de vista

- (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

    

valoraciones y comentarios

Recuerda algo, que puedes permitirte comentar si topaste tu pregunta en el momento justo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *