En général, si nous voulons avoir un lien cliquable dans le texte affiché par UILabel, nous devons résoudre deux tâches indépendantes:
- Modification de l'apparence d'une partie du texte pour ressembler à un lien
- Détection et traitement des contacts sur le lien (l'ouverture d'une URL est un cas particulier)
Le premier est simple. À partir d'iOS 6, UILabel prend en charge l'affichage des chaînes attribuées. Tout ce que vous devez faire est de créer et de configurer une instance de NSMutableAttributedString:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"String with a link" attributes:nil];
NSRange linkRange = NSMakeRange(14, 4); // for the word "link" in the string above
NSDictionary *linkAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithRed:0.05 green:0.4 blue:0.65 alpha:1.0],
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle) };
[attributedString setAttributes:linkAttributes range:linkRange];
// Assign attributedText to UILabel
label.attributedText = attributedString;
C'est tout! Le code ci-dessus fait qu'UILabel affiche la chaîne avec un lien
Maintenant, nous devons détecter les contacts sur ce lien. L'idée est d'attraper tous les taps dans UILabel et de déterminer si l'emplacement du tap était suffisamment proche du lien. Pour attraper des touches, nous pouvons ajouter une reconnaissance des gestes du robinet à l'étiquette. Assurez-vous d'activer userInteraction pour l'étiquette, elle est désactivée par défaut:
label.userInteractionEnabled = YES;
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]];
Maintenant, le truc le plus sophistiqué: savoir si le robinet était sur l'endroit où le lien est affiché et non sur une autre partie de l'étiquette. Si nous avions un UILabel à ligne unique, cette tâche pourrait être résolue relativement facilement en codant en dur les limites de la zone où le lien est affiché, mais résolvons ce problème plus élégamment et pour le cas général - UILabel multiligne sans connaissance préalable de la disposition du lien.
L'une des approches consiste à utiliser les capacités de l'API Text Kit introduite dans iOS 7:
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
// Configure layoutManager and textStorage
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
// Configure textContainer
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = label.lineBreakMode;
textContainer.maximumNumberOfLines = label.numberOfLines;
Enregistrez les instances créées et configurées de NSLayoutManager, NSTextContainer et NSTextStorage dans les propriétés de votre classe (très probablement le descendant de UIViewController) - nous en aurons besoin dans d'autres méthodes.
Maintenant, chaque fois que l'étiquette change de cadre, mettez à jour la taille de textContainer:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.textContainer.size = self.label.bounds.size;
}
Et enfin, détectez si le robinet était exactement sur le lien:
- (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture
{
CGPoint locationOfTouchInLabel = [tapGesture locationInView:tapGesture.view];
CGSize labelSize = tapGesture.view.bounds.size;
CGRect textBoundingBox = [self.layoutManager usedRectForTextContainer:self.textContainer];
CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
locationOfTouchInLabel.y - textContainerOffset.y);
NSInteger indexOfCharacter = [self.layoutManager characterIndexForPoint:locationOfTouchInTextContainer
inTextContainer:self.textContainer
fractionOfDistanceBetweenInsertionPoints:nil];
NSRange linkRange = NSMakeRange(14, 4); // it's better to save the range somewhere when it was originally used for marking link in attributed string
if (NSLocationInRange(indexOfCharacter, linkRange)) {
// Open an URL, or handle the tap on the link in any other way
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://stackoverflow.com/"]];
}
}
Swift 4
solution entièrement fonctionnelle . Il utiliseUITextView
mais le fait se comporter comme unUILabel
. J'ai essayé les solutions ici et je n'ai pas réussi à obtenir une détection de lien précise.