Вот раскрывающаяся категория Objective-C, которая позволяет нажимать ссылки в существующих строках UILabel.attributedText
, используя существующий атрибут NSLinkAttributeName
.
@interface UILabel (GSBClickableLinks) <UIGestureRecognizerDelegate>
@property BOOL enableLinks;
@end
#import <objc/runtime.h>
static const void *INDEX;
static const void *TAP;
@implementation UILabel (GSBClickableLinks)
- (void)setEnableLinks:(BOOL)enableLinks
{
UITapGestureRecognizer *tap = objc_getAssociatedObject(self, &TAP); // retreive tap
if (enableLinks && !tap) { // add a gestureRegonzier to the UILabel to detect taps
tap = [UITapGestureRecognizer.alloc initWithTarget:self action:@selector(openLink)];
tap.delegate = self;
[self addGestureRecognizer:tap];
objc_setAssociatedObject(self, &TAP, tap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // save tap
}
self.userInteractionEnabled = enableLinks; // note - when false UILAbel wont receive taps, hence disable links
}
- (BOOL)enableLinks
{
return (BOOL)objc_getAssociatedObject(self, &TAP); // ie tap != nil
}
// First check whether user tapped on a link within the attributedText of the label.
// If so, then the our label's gestureRecogizer will subsequently fire, and open the corresponding NSLinkAttributeName.
// If not, then the tap will get passed along, eg to the enclosing UITableViewCell...
// Note: save which character in the attributedText was clicked so that we dont have to redo everything again in openLink.
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer != objc_getAssociatedObject(self, &TAP)) return YES; // dont block other gestures (eg swipe)
// Re-layout the attributedText to find out what was tapped
NSTextContainer *textContainer = [NSTextContainer.alloc initWithSize:self.frame.size];
textContainer.lineFragmentPadding = 0;
textContainer.maximumNumberOfLines = self.numberOfLines;
textContainer.lineBreakMode = self.lineBreakMode;
NSLayoutManager *layoutManager = NSLayoutManager.new;
[layoutManager addTextContainer:textContainer];
NSTextStorage *textStorage = [NSTextStorage.alloc initWithAttributedString:self.attributedText];
[textStorage addLayoutManager:layoutManager];
NSUInteger index = [layoutManager characterIndexForPoint:[gestureRecognizer locationInView:self]
inTextContainer:textContainer
fractionOfDistanceBetweenInsertionPoints:NULL];
objc_setAssociatedObject(self, &INDEX, @(index), OBJC_ASSOCIATION_RETAIN_NONATOMIC); // save index
return (BOOL)[self.attributedText attribute:NSLinkAttributeName atIndex:index effectiveRange:NULL]; // tapped on part of a link?
}
- (void)openLink
{
NSUInteger index = [objc_getAssociatedObject(self, &INDEX) unsignedIntegerValue]; // retrieve index
NSURL *url = [self.attributedText attribute:NSLinkAttributeName atIndex:index effectiveRange:NULL];
if (url && [UIApplication.sharedApplication canOpenURL:url]) [UIApplication.sharedApplication openURL:url];
}
@end
Это было бы немного чище, если бы вы делали это с помощью подкласса UILabel (т. Е. Ни одного из беспорядков objc_getAssociatedObject), но если вы похожи на меня, вы предпочитаете избегать создания ненужных (сторонних) подклассов, просто чтобы добавить некоторые дополнительные функции к существующим UIKit классы. Кроме того, это прекрасно, что он добавляет кликабельные ссылки на любой существующий UILabel, например, существующий UITableViewCells
!
Я попытался сделать его как можно менее инвазивным, используя существующий атрибут NSLinkAttributeName
, уже доступный в NSAttributedString. Так что это просто, как:
NSURL *myURL = [NSURL URLWithString:@"http://www.google.com"];
NSMutableAttributedString *myString = [NSMutableAttributedString.alloc initWithString:@"This string has a clickable link: "];
[myString appendAttributedString:[NSAttributedString.alloc initWithString:@"click here" attributes:@{NSLinkAttributeName:myURL}]];
...
myLabel.attributedText = myString;
myLabel.enableLinks = YES; // yes, that's all! :-)
По сути, это работает путем добавления UIGestureRecognizer
к вашей UILabel. Тяжелая работа сделана в gestureRecognizerShouldBegin:
, который перераспределяет строку attribuText, чтобы выяснить, какой символ был нажат. Если этот символ был частью NSLinkAttributeName, впоследствии gestRecognizer будет запускаться, извлекать соответствующий URL (из значения NSLinkAttributeName) и открывать ссылку в соответствии с обычным процессом [UIApplication.sharedApplication openURL:url]
.
Примечание. Делая все это в gestureRecognizerShouldBegin:
, если вы случайно не нажмете ссылку в ярлыке, событие передается. Так, например, ваш UITableViewCell будет захватывать нажатия на ссылки, но в остальном будет вести себя нормально (выберите ячейку, отмените выбор, прокрутите, ...).
Я поместил это в репозиторий GitHub здесь .
Адаптировано из SO Kai Burghardt здесь .