In generale, se vogliamo avere un link cliccabile nel testo visualizzato da UILabel, dovremmo risolvere due attività indipendenti:
- Modifica dell'aspetto di una parte del testo in modo che assomigli a un collegamento
- Rilevamento e gestione di tocchi sul collegamento (l'apertura di un URL è un caso particolare)
Il primo è facile. A partire da iOS 6 UILabel supporta la visualizzazione di stringhe attribuite. Tutto quello che devi fare è creare e configurare un'istanza di 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;
Questo è tutto! Il codice sopra riportato consente a UILabel di visualizzare String con un collegamento
Ora dovremmo rilevare tocchi su questo link. L'idea è quella di catturare tutti i tocchi all'interno di UILabel e capire se la posizione del rubinetto era abbastanza vicina al collegamento. Per cogliere tocchi possiamo aggiungere il riconoscimento dei gesti dei tocchi all'etichetta. Assicurati di abilitare userInteraction per l'etichetta, è disattivata per impostazione predefinita:
label.userInteractionEnabled = YES;
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]];
Ora le cose più sofisticate: scoprire se il tocco era su dove è visualizzato il collegamento e non su qualsiasi altra parte dell'etichetta. Se avessimo UILabel a riga singola, questo compito potrebbe essere risolto relativamente facilmente codificando i limiti dell'area in cui viene visualizzato il collegamento, ma risolviamo questo problema in modo più elegante e per il caso generale: UILabel multilinea senza una conoscenza preliminare del layout del collegamento.
Uno degli approcci è utilizzare le funzionalità dell'API di Text Kit introdotte in 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;
Salva le istanze create e configurate di NSLayoutManager, NSTextContainer e NSTextStorage nelle proprietà della tua classe (molto probabilmente il discendente di UIViewController) - ne avremo bisogno in altri metodi.
Ora, ogni volta che l'etichetta cambia cornice, aggiorna le dimensioni di TextContainer:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.textContainer.size = self.label.bounds.size;
}
E infine, rileva se il tocco era esattamente sul link:
- (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
soluzione completamente funzionante . UsaUITextView
ma lo fa comportare come unUILabel
. Ho provato le soluzioni qui e non sono riuscito a ottenere un rilevamento accurato dei collegamenti.