129 votes

La détection des robinets sur attribuée texte dans un UITextView sur iOS 7

J'ai un UITextView qui affiche une NSAttributedString. Cette chaîne contient des mots que je voudrais faire tappable, de sorte que quand ils sont taraudés, je suis sollicité pour que je peut effectuer une action. Je me rends compte que UITextView peut détecter appuie sur une URL et un appel de retour de mon délégué, mais ce ne sont pas des Url...

Il me semble qu'avec iOS7 et la puissance de TextKit cela devrait maintenant être possible, mais je ne peux pas trouver des exemples, et je ne suis pas sûr où commencer.

Je comprends qu'il est maintenant possible de créer des attributs personnalisés dans la chaîne (bien que je n'ai pas fait encore), et peut-être qu'elles seront utiles pour détecter si l'un des mots magiques a été exploité? En tout cas, je n'ai toujours pas maintenant comment intercepter le robinet et de détecter sur le mot qui le robinet s'est produite.

Quelqu'un a une solution?

Notez que iOS 6 la compatibilité n'est pas nécessaire.

121voto

tarmes Points 6363

Je voulais juste aider les autres un peu plus. A la suite de Shmidt de réponse, il est possible de faire exactement comme je l'avais demandé dans ma question initiale.

1) Créer un attribuées chaîne avec des attributs personnalisés appliqué à la cliquables mots. par exemple.

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

2) Créer un UITextView pour afficher cette chaîne, et ajouter un UITapGestureRecognizer. Ensuite la poignée du robinet:

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

    }
}

Si facile quand vous savez comment!

32voto

natenash203 Points 309

C'est une version légèrement modifiée, construction de @tarmes de réponse. Je ne pouvais pas obtenir l' valuevariable retourne rien, mais null sans le tweak ci-dessous. Aussi, j'avais besoin de la pleine attribut dictionnaire retourné afin de déterminer l'action qui en découle. J'aurais mis cela dans les commentaires, mais ne semble pas avoir le rep de le faire. Toutes mes excuses à l'avance si j'ai violé le protocole.

Spécifique tweak est d'utiliser textView.textStorage au lieu de textView.attributedText. Comme toujours en période d'apprentissage iOS programmeur, je ne suis pas vraiment sûr pourquoi, mais peut-être quelqu'un d'autre peut nous éclairer.

Modification spécifique dans le robinet de la méthode de gestion:

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

Le code complet de mon point de vue contrôleur

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

    }
}

26voto

Aditya Mathur Points 370

Faire lien personnalisé et de faire ce que vous voulez sur le robinet est devenu beaucoup plus facile avec iOS 7. Il est un très bon exemple à Ray Wenderlich

13voto

Shmidt Points 5549

La WWDC 2013 exemple:

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

4voto

Arkadiusz Holko Points 4569

Il est possible de le faire avec characterIndexForPoint:inTextContainer:fractionOfDistanceBetweenInsertionPoints:. Il vous fonctionne un peu différemment que vous vouliez - vous aurez pour tester si un taraudés personnage appartient à un mot magique. Mais il ne devrait pas être compliqué.

BTW, je recommande fortement de regarder l'Introduction du Texte le Kit de la WWDC 2013.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X