在Uilabel的NSAttributedString中创建可点击的“链接”?
-
12-09-2019 - |
题
我已经搜索了几个小时,但我失败了。我什至不知道我应该寻找什么。
许多应用程序具有文本,在此文本中是圆形RECT的Web超链接。当我单击它们时 UIWebView
打开。让我感到困惑的是,它们通常具有自定义链接,例如,如果单词以#开头,则它也可以单击,并且应用程序通过打开另一个视图做出响应。我怎样才能做到这一点?有可能与 UILabel
还是我需要 UITextView
或者是其他东西?
解决方案
通常,如果我们想在Uilabel显示的文本中具有可单击的链接,我们将需要解决两个独立的任务:
- 更改文本的一部分的外观看起来像链接
- 在链接上检测和处理触摸(打开URL是一种特殊情况)
第一个很容易。从iOS 6开始,Uilabel支持显示属性字符串。您需要做的就是创建和配置NSMutableAbeatTributedString的实例:
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;
而已!上面的代码使Uilabel显示 带有A的字符串 关联
现在,我们应该检测到此链接的触摸。这个想法是要捕获Uilabel中的所有水龙头,并弄清楚水龙头的位置是否足够接近链接。为了捕捉触摸,我们可以在标签中添加点击手势识别器。确保启用标签的用户互动,默认情况下关闭了:
label.userInteractionEnabled = YES;
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]];
现在是最复杂的东西:找出水龙头是否在链接显示的位置,而不是标签的其他部分。如果我们拥有单层的Uilabel,则可以通过硬编码显示链接的区域范围来相对简单地解决此任务,但让我们更优雅地解决此问题,对于一般情况 - 多行uilabel而没有有关链接布局的初步知识。
其中一种方法是使用iOS 7中引入的文本套件API的功能:
// 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;
在您的类中(最有可能是UiviewController的后代)中保存NSlayoutManager,NstextContainer和NstextStorage的创建和配置的实例 - 我们将以其他方法需要它们。
现在,每次标签更改其框架时,都会更新TextContainer的大小:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.textContainer.size = self.label.bounds.size;
}
最后,检测水龙头是否完全在链接上:
- (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/"]];
}
}
其他提示
我正在扩展 @nalexn 原始详细解决方案,带有 @zekel 出色的扩展 UITapGestureRecognizer
, ,并提供 迅速.
扩展uitapgesturerercognizer
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(
x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y
)
let locationOfTouchInTextContainer = CGPoint(
x: locationOfTouchInLabel.x - textContainerOffset.x,
y: locationOfTouchInLabel.y - textContainerOffset.y
)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
用法
设置 UIGestureRecognizer
将行动发送给 tapLabel:
, ,您可以检测到目标范围是否被列入 myLabel
.
@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange1) {
print("Tapped targetRange1")
} else if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange2) {
print("Tapped targetRange2")
} else {
print("Tapped none")
}
}
重要的是: UILabel
必须设置断路模式以通过Word/Char包裹。不知何故, NSTextContainer
仅当换行模式为否则,才假定文本是单行。
旧问题,但是是否有人可以使用 UITextView
代替 UILabel
, ,那很容易。将自动检测到标准URL,电话号码等(可单击)。
但是,如果您需要自定义检测 NSAttributedStrings
带着 NSLinkAttributeName
将指向自定义URL方案的属性(默认情况下具有HTTP URL方案相反)。 雷·温德利希(Ray Wenderlich)
引用上述链接中的代码:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"This is an example by @marcelofabri_"];
[attributedString addAttribute:NSLinkAttributeName
value:@"username://marcelofabri_"
range:[[attributedString string] rangeOfString:@"@marcelofabri_"]];
NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor greenColor],
NSUnderlineColorAttributeName: [UIColor lightGrayColor],
NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};
// assume that textView is a UITextView previously created (either by code or Interface Builder)
textView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;
textView.delegate = self;
要检测这些链接点击,请实现以下操作:
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
if ([[URL scheme] isEqualToString:@"username"]) {
NSString *username = [URL host];
// do something with this username
// ...
return NO;
}
return YES; // let the system open this URL
}
PS:确保您的 UITextView
是 selectable
.
如果您没有为其设置任何图像,则UibuttonTyPecustom是可单击的标签。
(我的答案建立在 @nalexn的基础上 出色的答案. 。我不会在这里复制他对每个步骤的详细解释。)
我发现最方便,最直接,可以增加对Uilabel文本的支持,作为Uitapgesturerecognizer的类别。 (你没有 有 如某些答案所示,使用UITEXTVIEW的数据检测器。)
将以下方法添加到您的UITAPGENTURERERECOGNIZER类别:
/**
Returns YES if the tap gesture was within the specified range of the attributed text of the label.
*/
- (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange {
NSParameterAssert(label != nil);
CGSize labelSize = label.bounds.size;
// create instances of NSLayoutManager, NSTextContainer and NSTextStorage
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
// configure layoutManager and textStorage
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
// configure textContainer for the label
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = label.lineBreakMode;
textContainer.maximumNumberOfLines = label.numberOfLines;
textContainer.size = labelSize;
// find the tapped character location and compare it to the specified range
CGPoint locationOfTouchInLabel = [self locationInView:label];
CGRect textBoundingBox = [layoutManager usedRectForTextContainer: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 = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer
inTextContainer:textContainer
fractionOfDistanceBetweenInsertionPoints:nil];
if (NSLocationInRange(indexOfCharacter, targetRange)) {
return YES;
} else {
return NO;
}
}
示例代码
// (in your view controller)
// create your label, gesture recognizer, attributed text, and get the range of the "link" in your label
myLabel.userInteractionEnabled = YES;
[myLabel addGestureRecognizer:
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleTapOnLabel:)]];
// create your attributed text and keep an ivar of your "link" text range
NSAttributedString *plainText;
NSAttributedString *linkText;
plainText = [[NSMutableAttributedString alloc] initWithString:@"Add label links with UITapGestureRecognizer"
attributes:nil];
linkText = [[NSMutableAttributedString alloc] initWithString:@" Learn more..."
attributes:@{
NSForegroundColorAttributeName:[UIColor blueColor]
}];
NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] init];
[attrText appendAttributedString:plainText];
[attrText appendAttributedString:linkText];
// ivar -- keep track of the target range so you can compare in the callback
targetRange = NSMakeRange(plainText.length, linkText.length);
手势回调
// handle the gesture recognizer callback and call the category method
- (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture {
BOOL didTapLink = [tapGesture didTapAttributedTextInLabel:myLabel
inRange:targetRange];
NSLog(@"didTapLink: %d", didTapLink);
}
UITextView
支持OS3.0中的数据检测器,而 UILabel
不。
如果您在 UITextView
您的文本包含URL,电话号码等。它们将以链接出现。
将 @samwize的扩展转换为Swift 4:
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
guard let attrString = label.attributedText else {
return false
}
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
let textStorage = NSTextStorage(attributedString: attrString)
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
设置识别器(一旦您为文本和内容着色):
lblTermsOfUse.isUserInteractionEnabled = true
lblTermsOfUse.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOnLabel(_:))))
...然后手势识别器:
@objc func handleTapOnLabel(_ recognizer: UITapGestureRecognizer) {
guard let text = lblAgreeToTerms.attributedText?.string else {
return
}
if let range = text.range(of: NSLocalizedString("_onboarding_terms", comment: "terms")),
recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) {
goToTermsAndConditions()
} else if let range = text.range(of: NSLocalizedString("_onboarding_privacy", comment: "privacy")),
recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) {
goToPrivacyPolicy()
}
}
正如我在 这个帖子, ,这是我专门为uilabel链接创建的轻加权库 弗林贝尔.
为了实现这样的效果:
洛雷姆 ipsum dolor坐在amet,consectetur adipiscing elit。 Pellentesque 奎斯 布兰迪特 Eros,坐在Amet Vehicula Justo。 NAM在Urna Neque。 保护者 AC SEM EU SEM PORTA DESTAUM NEC VEL TELLUS。
使用代码:
//Step 1: Define a normal attributed string for non-link texts
NSString *string = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque quis blandit eros, sit amet vehicula justo. Nam at urna neque. Maecenas ac sem eu sem porta dictum nec vel tellus.";
NSDictionary *attributes = @{NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]};
label.attributedText = [[NSAttributedString alloc]initWithString:string attributes:attributes];
//Step 2: Define a selection handler block
void(^handler)(FRHyperLabel *label, NSString *substring) = ^(FRHyperLabel *label, NSString *substring){
NSLog(@"Selected: %@", substring);
};
//Step 3: Add link substrings
[label setLinksForSubstrings:@[@"Lorem", @"Pellentesque", @"blandit", @"Maecenas"] withLinkHandler:handler];
我创建了uilabel子类名称 竞争标签 它基于iOS 7中引入的TextKit API。它使用了相同的方法。 nalexn. 。它可以灵活地指定在文本中搜索的模式。可以指定要应用于这些模式的样式,以及在敲击模式时执行的操作。
//Detects email in text
NSString *emailRegexString = @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}";
NSError *error;
NSRegularExpression *regex = [[NSRegularExpression alloc]initWithPattern:emailRegexString options:0 error:&error];
PatternDescriptor *descriptor = [[PatternDescriptor alloc]initWithRegex:regex withSearchType:PatternSearchTypeAll withPatternAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]}];
[self.customLabel enablePatternDetection:descriptor];
如果要单击字符串,可以这样做。此代码将属性应用于字符串“文本”的每个出现。
PatternTapResponder tapResponder = ^(NSString *string) {
NSLog(@"tapped = %@",string);
};
[self.customLabel enableStringDetection:@"text" withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],
RLTapResponderAttributeName: tapResponder}];
在Swift 3中工作,在这里粘贴整个代码
//****Make sure the textview 'Selectable' = checked, and 'Editable = Unchecked'
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
@IBOutlet var theNewTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
//****textview = Selectable = checked, and Editable = Unchecked
theNewTextView.delegate = self
let theString = NSMutableAttributedString(string: "Agree to Terms")
let theRange = theString.mutableString.range(of: "Terms")
theString.addAttribute(NSLinkAttributeName, value: "ContactUs://", range: theRange)
let theAttribute = [NSForegroundColorAttributeName: UIColor.blue, NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue] as [String : Any]
theNewTextView.linkTextAttributes = theAttribute
theNewTextView.attributedText = theString
theString.setAttributes(theAttribute, range: theRange)
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if (URL.scheme?.hasPrefix("ContactUs://"))! {
return false //interaction not allowed
}
//*** Set storyboard id same as VC name
self.navigationController!.pushViewController((self.storyboard?.instantiateViewController(withIdentifier: "TheLastViewController"))! as UIViewController, animated: true)
return true
}
}
这是超链接Uilabel的示例代码:来源:http://sickprogrammersarea.blogspot.in/2014/03/adding-links-to-uilabel.html
#import "ViewController.h"
#import "TTTAttributedLabel.h"
@interface ViewController ()
@end
@implementation ViewController
{
UITextField *loc;
TTTAttributedLabel *data;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(5, 20, 80, 25) ];
[lbl setText:@"Text:"];
[lbl setFont:[UIFont fontWithName:@"Verdana" size:16]];
[lbl setTextColor:[UIColor grayColor]];
loc=[[UITextField alloc] initWithFrame:CGRectMake(4, 20, 300, 30)];
//loc.backgroundColor = [UIColor grayColor];
loc.borderStyle=UITextBorderStyleRoundedRect;
loc.clearButtonMode=UITextFieldViewModeWhileEditing;
//[loc setText:@"Enter Location"];
loc.clearsOnInsertion = YES;
loc.leftView=lbl;
loc.leftViewMode=UITextFieldViewModeAlways;
[loc setDelegate:self];
[self.view addSubview:loc];
[loc setRightViewMode:UITextFieldViewModeAlways];
CGRect frameimg = CGRectMake(110, 70, 70,30);
UIButton *srchButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
srchButton.frame=frameimg;
[srchButton setTitle:@"Go" forState:UIControlStateNormal];
[srchButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
srchButton.backgroundColor=[UIColor clearColor];
[srchButton addTarget:self action:@selector(go:) forControlEvents:UIControlEventTouchDown];
[self.view addSubview:srchButton];
data = [[TTTAttributedLabel alloc] initWithFrame:CGRectMake(5, 120,self.view.frame.size.width,200) ];
[data setFont:[UIFont fontWithName:@"Verdana" size:16]];
[data setTextColor:[UIColor blackColor]];
data.numberOfLines=0;
data.delegate = self;
data.enabledTextCheckingTypes=NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber;
[self.view addSubview:data];
}
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url
{
NSString *val=[[NSString alloc]initWithFormat:@"%@",url];
if ([[url scheme] hasPrefix:@"mailto"]) {
NSLog(@" mail URL Selected : %@",url);
MFMailComposeViewController *comp=[[MFMailComposeViewController alloc]init];
[comp setMailComposeDelegate:self];
if([MFMailComposeViewController canSendMail])
{
NSString *recp=[[val substringToIndex:[val length]] substringFromIndex:7];
NSLog(@"Recept : %@",recp);
[comp setToRecipients:[NSArray arrayWithObjects:recp, nil]];
[comp setSubject:@"From my app"];
[comp setMessageBody:@"Hello bro" isHTML:NO];
[comp setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:comp animated:YES completion:nil];
}
}
else{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:val]];
}
}
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{
if(error)
{
UIAlertView *alrt=[[UIAlertView alloc]initWithTitle:@"Erorr" message:@"Some error occureed" delegate:nil cancelButtonTitle:@"" otherButtonTitles:nil, nil];
[alrt show];
[self dismissViewControllerAnimated:YES completion:nil];
}
else{
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber
{
NSLog(@"Phone Number Selected : %@",phoneNumber);
UIDevice *device = [UIDevice currentDevice];
if ([[device model] isEqualToString:@"iPhone"] ) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tel:%@",phoneNumber]]];
} else {
UIAlertView *Notpermitted=[[UIAlertView alloc] initWithTitle:@"Alert" message:@"Your device doesn't support this feature." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[Notpermitted show];
}
}
-(void)go:(id)sender
{
[data setText:loc.text];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Reached");
[loc resignFirstResponder];
}
这是Nalexn答案的迅速版本。
class TapabbleLabel: UILabel {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
var textStorage = NSTextStorage() {
didSet {
textStorage.addLayoutManager(layoutManager)
}
}
var onCharacterTapped: ((label: UILabel, characterIndex: Int) -> Void)?
let tapGesture = UITapGestureRecognizer()
override var attributedText: NSAttributedString? {
didSet {
if let attributedText = attributedText {
textStorage = NSTextStorage(attributedString: attributedText)
} else {
textStorage = NSTextStorage()
}
}
}
override var lineBreakMode: NSLineBreakMode {
didSet {
textContainer.lineBreakMode = lineBreakMode
}
}
override var numberOfLines: Int {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
}
}
/**
Creates a new view with the passed coder.
:param: aDecoder The a decoder
:returns: the created new view.
*/
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
/**
Creates a new view with the passed frame.
:param: frame The frame
:returns: the created new view.
*/
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
/**
Sets up the view.
*/
func setUp() {
userInteractionEnabled = true
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
tapGesture.addTarget(self, action: #selector(TapabbleLabel.labelTapped(_:)))
addGestureRecognizer(tapGesture)
}
override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = bounds.size
}
func labelTapped(gesture: UITapGestureRecognizer) {
guard gesture.state == .Ended else {
return
}
let locationOfTouch = gesture.locationInView(gesture.view)
let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
let textContainerOffset = CGPoint(x: (bounds.width - textBoundingBox.width) / 2 - textBoundingBox.minX,
y: (bounds.height - textBoundingBox.height) / 2 - textBoundingBox.minY) let locationOfTouchInTextContainer = CGPoint(x: locationOfTouch.x - textContainerOffset.x,
y: locationOfTouch.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer,
inTextContainer: textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
onCharacterTapped?(label: self, characterIndex: indexOfCharacter)
}
}
然后,您可以在您的内部创建该类的实例 viewDidLoad
这样的方法:
let label = TapabbleLabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[view]-|",
options: [], metrics: nil, views: ["view" : label]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[view]-|",
options: [], metrics: nil, views: ["view" : label]))
let attributedString = NSMutableAttributedString(string: "String with a link", attributes: nil)
let linkRange = NSMakeRange(14, 4); // for the word "link" in the string above
let linkAttributes: [String : AnyObject] = [
NSForegroundColorAttributeName : UIColor.blueColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleSingle.rawValue,
NSLinkAttributeName: "http://www.apple.com"]
attributedString.setAttributes(linkAttributes, range:linkRange)
label.attributedText = attributedString
label.onCharacterTapped = { label, characterIndex in
if let attribute = label.attributedText?.attribute(NSLinkAttributeName, atIndex: characterIndex, effectiveRange: nil) as? String,
let url = NSURL(string: attribute) {
UIApplication.sharedApplication().openURL(url)
}
}
最好有一个自定义属性来轻拍字符时使用的自定义属性。现在,这是 NSLinkAttributeName
, ,但可以是任何事情,您可以使用该值来做其他事情,除了打开URL,您可以执行任何自定义操作。
就像较早的Awnser中所报道的那样,UITEXTVIEW能够处理链接的触摸。通过使文本的其他部分作为链接起作用,可以轻松扩展这一点。属性TextView库是一个UITEXTVIEW子类,使处理它们非常容易。有关更多信息,请参见: https://github.com/evermeer/attributedtextview
您可以像这样使文本的任何部分进行交互(其中TextView1是UITEXTVIEW IBOUTLET):
textView1.attributer =
"1. ".red
.append("This is the first test. ").green
.append("Click on ").black
.append("evict.nl").makeInteract { _ in
UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { completed in })
}.underline
.append(" for testing links. ").black
.append("Next test").underline.makeInteract { _ in
print("NEXT")
}
.all.font(UIFont(name: "SourceSansPro-Regular", size: 16))
.setLinkColor(UIColor.purple)
对于处理主题标签和提及,您可以使用这样的代码:
textView1.attributer = "@test: What #hashtags do we have in @evermeer #AtributedTextView library"
.matchHashtags.underline
.matchMentions
.makeInteract { link in
UIApplication.shared.open(URL(string: "https://twitter.com\(link.replacingOccurrences(of: "@", with: ""))")!, options: [:], completionHandler: { completed in })
}
我很难处理这个... uilabel在属性文本上有链接上的链接...这只是头痛,所以我最终使用了 ZSWTAPPABLELELABEL.
我正在扩展 @samwize的答案,以处理多行uilabel,并举例说明使用uibutton
extension UITapGestureRecognizer {
func didTapAttributedTextInButton(button: UIButton, inRange targetRange: NSRange) -> Bool {
guard let label = button.titleLabel else { return false }
return didTapAttributedTextInLabel(label, inRange: targetRange)
}
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.locationInView(label)
let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
let locationOfTouchInTextContainer = CGPointMake((locationOfTouchInLabel.x - textContainerOffset.x),
0 );
// Adjust for multiple lines of text
let lineModifier = Int(ceil(locationOfTouchInLabel.y / label.font.lineHeight)) - 1
let rightMostFirstLinePoint = CGPointMake(labelSize.width, 0)
let charsPerLine = layoutManager.characterIndexForPoint(rightMostFirstLinePoint, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let adjustedRange = indexOfCharacter + (lineModifier * charsPerLine)
return NSLocationInRange(adjustedRange, targetRange)
}
}
对于完全自定义的链接,您需要使用UIWebView-您可以拦截电话,以便在按下链接时可以转到应用程序的其他部分。
这是一个插入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_GetAssivicationObject Mess)完成的,但是如果您像我一样,您宁愿避免不必为现有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
到你的乌拉贝尔。辛勤工作是在 gestureRecognizerShouldBegin:
, ,它重新分配属性文本字符串,以找出被敲击的字符。如果此字符是NSlinkAttributeName的一部分,则gesturerognizer将随后发射,检索相应的URL(从NSlinkAttributeName值),然后按照通常的方式打开链接 [UIApplication.sharedApplication openURL:url]
过程。
注意 - 通过 gestureRecognizerShouldBegin:
, ,如果您不碰巧点击标签中的链接,则该事件将通过。因此,例如,您的UitableViewCell将捕获链接上的水龙头,但以其他方式行为(选择单元格,取消选择,滚动,...)。
我遵循此版本,
Swift 4:
import Foundation
class AELinkedClickableUILabel: UILabel {
typealias YourCompletion = () -> Void
var linkedRange: NSRange!
var completion: YourCompletion?
@objc func linkClicked(sender: UITapGestureRecognizer){
if let completionBlock = completion {
let textView = UITextView(frame: self.frame)
textView.text = self.text
textView.attributedText = self.attributedText
let index = textView.layoutManager.characterIndex(for: sender.location(in: self),
in: textView.textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
if linkedRange.lowerBound <= index && linkedRange.upperBound >= index {
completionBlock()
}
}
}
/**
* This method will be used to set an attributed text specifying the linked text with a
* handler when the link is clicked
*/
public func setLinkedTextWithHandler(text:String, link: String, handler: @escaping ()->()) -> Bool {
let attributextText = NSMutableAttributedString(string: text)
let foundRange = attributextText.mutableString.range(of: link)
if foundRange.location != NSNotFound {
self.linkedRange = foundRange
self.completion = handler
attributextText.addAttribute(NSAttributedStringKey.link, value: text, range: foundRange)
self.isUserInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(linkClicked(sender:))))
return true
}
return false
}
}
致电示例:
button.setLinkedTextWithHandler(text: "This website (stackoverflow.com) is awesome", link: "stackoverflow.com")
{
// show popup or open to link
}
使用以下.h和.m文件创建类。在.m文件中有以下功能
- (void)linkAtPoint:(CGPoint)location
在此功能内部,我们将检查我们需要采取行动的子字符串范围。使用自己的逻辑来放置范围。
以下是子类的使用
TaggedLabel *label = [[TaggedLabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:label];
label.numberOfLines = 0;
NSMutableAttributedString *attributtedString = [[NSMutableAttributedString alloc] initWithString : @"My name is @jjpp" attributes : @{ NSFontAttributeName : [UIFont systemFontOfSize:10],}];
//Do not forget to add the font attribute.. else it wont work.. it is very important
[attributtedString addAttribute:NSForegroundColorAttributeName
value:[UIColor redColor]
range:NSMakeRange(11, 5)];//you can give this range inside the .m function mentioned above
以下是.h文件
#import <UIKit/UIKit.h>
@interface TaggedLabel : UILabel<NSLayoutManagerDelegate>
@property(nonatomic, strong)NSLayoutManager *layoutManager;
@property(nonatomic, strong)NSTextContainer *textContainer;
@property(nonatomic, strong)NSTextStorage *textStorage;
@property(nonatomic, strong)NSArray *tagsArray;
@property(readwrite, copy) tagTapped nameTagTapped;
@end
以下是.m文件
#import "TaggedLabel.h"
@implementation TaggedLabel
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.userInteractionEnabled = YES;
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
self.userInteractionEnabled = YES;
}
return self;
}
- (void)setupTextSystem
{
_layoutManager = [[NSLayoutManager alloc] init];
_textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
_textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
// Configure layoutManager and textStorage
[_layoutManager addTextContainer:_textContainer];
[_textStorage addLayoutManager:_layoutManager];
// Configure textContainer
_textContainer.lineFragmentPadding = 0.0;
_textContainer.lineBreakMode = NSLineBreakByWordWrapping;
_textContainer.maximumNumberOfLines = 0;
self.userInteractionEnabled = YES;
self.textContainer.size = self.bounds.size;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!_layoutManager)
{
[self setupTextSystem];
}
// Get the info for the touched link if there is one
CGPoint touchLocation = [[touches anyObject] locationInView:self];
[self linkAtPoint:touchLocation];
}
- (void)linkAtPoint:(CGPoint)location
{
// Do nothing if we have no text
if (_textStorage.string.length == 0)
{
return;
}
// Work out the offset of the text in the view
CGPoint textOffset = [self calcGlyphsPositionInView];
// Get the touch location and use text offset to convert to text cotainer coords
location.x -= textOffset.x;
location.y -= textOffset.y;
NSUInteger touchedChar = [_layoutManager glyphIndexForPoint:location inTextContainer:_textContainer];
// If the touch is in white space after the last glyph on the line we don't
// count it as a hit on the text
NSRange lineRange;
CGRect lineRect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex:touchedChar effectiveRange:&lineRange];
if (CGRectContainsPoint(lineRect, location) == NO)
{
return;
}
// Find the word that was touched and call the detection block
NSRange range = NSMakeRange(11, 5);//for this example i'm hardcoding the range here. In a real scenario it should be iterated through an array for checking all the ranges
if ((touchedChar >= range.location) && touchedChar < (range.location + range.length))
{
NSLog(@"range-->>%@",self.tagsArray[i][@"range"]);
}
}
- (CGPoint)calcGlyphsPositionInView
{
CGPoint textOffset = CGPointZero;
CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
textBounds.size.width = ceil(textBounds.size.width);
textBounds.size.height = ceil(textBounds.size.height);
if (textBounds.size.height < self.bounds.size.height)
{
CGFloat paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2.0;
textOffset.y = paddingHeight;
}
if (textBounds.size.width < self.bounds.size.width)
{
CGFloat paddingHeight = (self.bounds.size.width - textBounds.size.width) / 2.0;
textOffset.x = paddingHeight;
}
return textOffset;
}
@end
根据查尔斯·赌博的答案,我使用的是(我删除了一些使我感到困惑并给我错误的索引的行):
- (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange TapGesture:(UIGestureRecognizer*) gesture{
NSParameterAssert(label != nil);
// create instances of NSLayoutManager, NSTextContainer and NSTextStorage
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
// configure layoutManager and textStorage
[textStorage addLayoutManager:layoutManager];
// configure textContainer for the label
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(label.frame.size.width, label.frame.size.height)];
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = label.lineBreakMode;
textContainer.maximumNumberOfLines = label.numberOfLines;
// find the tapped character location and compare it to the specified range
CGPoint locationOfTouchInLabel = [gesture locationInView:label];
[layoutManager addTextContainer:textContainer]; //(move here, not sure it that matter that calling this line after textContainer is set
NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInLabel
inTextContainer:textContainer
fractionOfDistanceBetweenInsertionPoints:nil];
if (NSLocationInRange(indexOfCharacter, targetRange)) {
return YES;
} else {
return NO;
}
}
这是一个迅速的实现,它尽可能最小,还包括触摸反馈。警告:
- 您必须在nsattribedstrings中设置字体
- 您只能使用nsattribedstrings!
- 您必须确保链接无法包装(使用非断裂空间:
"\u{a0}"
) - 设置文本后,您无法更改LineBreakMode或数字线
- 您通过添加属性来创建链接
.link
钥匙
.
public class LinkLabel: UILabel {
private var storage: NSTextStorage?
private let textContainer = NSTextContainer()
private let layoutManager = NSLayoutManager()
private var selectedBackgroundView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
textContainer.lineFragmentPadding = 0
layoutManager.addTextContainer(textContainer)
textContainer.layoutManager = layoutManager
isUserInteractionEnabled = true
selectedBackgroundView.isHidden = true
selectedBackgroundView.backgroundColor = UIColor(white: 0, alpha: 0.3333)
selectedBackgroundView.layer.cornerRadius = 4
addSubview(selectedBackgroundView)
}
public required convenience init(coder: NSCoder) {
self.init(frame: .zero)
}
public override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = frame.size
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
setLink(for: touches)
}
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
setLink(for: touches)
}
private func setLink(for touches: Set<UITouch>) {
if let pt = touches.first?.location(in: self), let (characterRange, _) = link(at: pt) {
let glyphRange = layoutManager.glyphRange(forCharacterRange: characterRange, actualCharacterRange: nil)
selectedBackgroundView.frame = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer).insetBy(dx: -3, dy: -3)
selectedBackgroundView.isHidden = false
} else {
selectedBackgroundView.isHidden = true
}
}
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
selectedBackgroundView.isHidden = true
}
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
selectedBackgroundView.isHidden = true
if let pt = touches.first?.location(in: self), let (_, url) = link(at: pt) {
UIApplication.shared.open(url)
}
}
private func link(at point: CGPoint) -> (NSRange, URL)? {
let touchedGlyph = layoutManager.glyphIndex(for: point, in: textContainer)
let touchedChar = layoutManager.characterIndexForGlyph(at: touchedGlyph)
var range = NSRange()
let attrs = attributedText!.attributes(at: touchedChar, effectiveRange: &range)
if let urlstr = attrs[.link] as? String {
return (range, URL(string: urlstr)!)
} else {
return nil
}
}
public override var attributedText: NSAttributedString? {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
textContainer.lineBreakMode = lineBreakMode
if let txt = attributedText {
storage = NSTextStorage(attributedString: txt)
storage!.addLayoutManager(layoutManager)
layoutManager.textStorage = storage
textContainer.size = frame.size
}
}
}
}
这种通用方法也有效!
func didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange targetRange: NSRange) -> Bool {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
guard let strAttributedText = self.attributedText else {
return false
}
let textStorage = NSTextStorage(attributedString: strAttributedText)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = Constants.lineFragmentPadding
textContainer.lineBreakMode = self.lineBreakMode
textContainer.maximumNumberOfLines = self.numberOfLines
let labelSize = self.bounds.size
textContainer.size = CGSize(width: labelSize.width, height: CGFloat.greatestFiniteMagnitude)
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = gesture.location(in: self)
let xCordLocationOfTouchInTextContainer = locationOfTouchInLabel.x
let yCordLocationOfTouchInTextContainer = locationOfTouchInLabel.y
let locOfTouch = CGPoint(x: xCordLocationOfTouchInTextContainer ,
y: yCordLocationOfTouchInTextContainer)
let indexOfCharacter = layoutManager.characterIndex(for: locOfTouch, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard let strLabel = text else {
return false
}
let charCountOfLabel = strLabel.count
if indexOfCharacter < (charCountOfLabel - 1) {
return NSLocationInRange(indexOfCharacter, targetRange)
} else {
return false
}
}
您可以用
let text = yourLabel.text
let termsRange = (text as NSString).range(of: fullString)
if yourLabel.didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange: termsRange) {
showCorrespondingViewController()
}
这里 是我基于@luca Davanzo的答案 回答, ,覆盖 touchesBegan
事件而不是踢踏手势:
import UIKit
public protocol TapableLabelDelegate: NSObjectProtocol {
func tapableLabel(_ label: TapableLabel, didTapUrl url: String, atRange range: NSRange)
}
public class TapableLabel: UILabel {
private var links: [String: NSRange] = [:]
private(set) var layoutManager = NSLayoutManager()
private(set) var textContainer = NSTextContainer(size: CGSize.zero)
private(set) var textStorage = NSTextStorage() {
didSet {
textStorage.addLayoutManager(layoutManager)
}
}
public weak var delegate: TapableLabelDelegate?
public override var attributedText: NSAttributedString? {
didSet {
if let attributedText = attributedText {
textStorage = NSTextStorage(attributedString: attributedText)
} else {
textStorage = NSTextStorage()
links = [:]
}
}
}
public override var lineBreakMode: NSLineBreakMode {
didSet {
textContainer.lineBreakMode = lineBreakMode
}
}
public override var numberOfLines: Int {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
public override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = bounds.size
}
/// addLinks
///
/// - Parameters:
/// - text: text of link
/// - url: link url string
public func addLink(_ text: String, withURL url: String) {
guard let theText = attributedText?.string as? NSString else {
return
}
let range = theText.range(of: text)
guard range.location != NSNotFound else {
return
}
links[url] = range
}
private func setup() {
isUserInteractionEnabled = true
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let locationOfTouch = touches.first?.location(in: self) else {
return
}
textContainer.size = bounds.size
let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)
for (urlString, range) in links {
if NSLocationInRange(indexOfCharacter, range), let url = URL(string: urlString) {
delegate?.tapableLabel(self, didTapUrl: urlString, atRange: range)
}
}
}}
我找到了其他解决方案:
我找到了一种方法来检测您从Internet中找到的HTML文本中的链接,然后将其转换为NSATTRIBUTESTRING:
func htmlAttributedString(fontSize: CGFloat = 17.0) -> NSAttributedString? {
let fontName = UIFont.systemFont(ofSize: fontSize).fontName
let string = self.appending(String(format: "<style>body{font-family: '%@'; font-size:%fpx;}</style>", fontName, fontSize))
guard let data = string.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
guard let html = try? NSMutableAttributedString (
data: data,
options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil) else { return nil }
return html
}
我的方法使您可以检测超链接而无需指定它们。
首先,您创建tapgesturerecognizer的扩展名:
extension UITapGestureRecognizer { func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool { guard let attrString = label.attributedText else { return false } let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: .zero) let textStorage = NSTextStorage(attributedString: attrString) layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) textContainer.lineFragmentPadding = 0 textContainer.lineBreakMode = label.lineBreakMode textContainer.maximumNumberOfLines = label.numberOfLines let labelSize = label.bounds.size textContainer.size = labelSize let locationOfTouchInLabel = self.location(in: label) let textBoundingBox = layoutManager.usedRect(for: textContainer) let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y) let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y) let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) return NSLocationInRange(indexOfCharacter, targetRange) }
}
然后,在您查看控制器中,您创建了一个URL列表和范围来存储属性文本所包含的所有链接和范围:
var listurl : [String] = []
var listURLRange : [NSRange] = []
要查找可以使用的URL和URLRENGE:
fun findLinksAndRange(attributeString : NSAttributeString){
notification.enumerateAttribute(NSAttributedStringKey.link , in: NSMakeRange(0, notification.length), options: [.longestEffectiveRangeNotRequired]) { value, range, isStop in
if let value = value {
print("\(value) found at \(range.location)")
let stringValue = "\(value)"
listurl.append(stringValue)
listURLRange.append(range)
}
}
westlandNotifcationLabel.addGestureRecognizer(UITapGestureRecognizer(target : self, action: #selector(handleTapOnLabel(_:))))
}
然后,您实现手柄点击:
@objc func handleTapOnLabel(_ recognizer: UITapGestureRecognizer) {
for index in 0..<listURLRange.count{
if recognizer.didTapAttributedTextInLabel(label: westlandNotifcationLabel, inRange: listURLRange[index]) {
goToWebsite(url : listurl[index])
}
}
}
func goToWebsite(url : String){
if let websiteUrl = URL(string: url){
if #available(iOS 10, *) {
UIApplication.shared.open(websiteUrl, options: [:],
completionHandler: {
(success) in
print("Open \(websiteUrl): \(success)")
})
} else {
let success = UIApplication.shared.openURL(websiteUrl)
print("Open \(websiteUrl): \(success)")
}
}
}
现在我们开始!
希望此解决方案能帮助您喜欢它。
标签 #swift2.0
我掌握了灵感 - 出色 - @nalexn的答案,我决定自己写一个uilabel的包装纸。
我也尝试了 tttattribedlabel 但是我无法使它起作用。
希望您能欣赏此代码,欢迎任何建议!
import Foundation
@objc protocol TappableLabelDelegate {
optional func tappableLabel(tabbableLabel: TappableLabel, didTapUrl: NSURL, atRange: NSRange)
}
/// Represent a label with attributed text inside.
/// We can add a correspondence between a range of the attributed string an a link (URL)
/// By default, link will be open on the external browser @see 'openLinkOnExternalBrowser'
class TappableLabel: UILabel {
// MARK: - Public properties -
var links: NSMutableDictionary = [:]
var openLinkOnExternalBrowser = true
var delegate: TappableLabelDelegate?
// MARK: - Constructors -
override func awakeFromNib() {
super.awakeFromNib()
self.enableInteraction()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.enableInteraction()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func enableInteraction() {
self.userInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("didTapOnLabel:")))
}
// MARK: - Public methods -
/**
Add correspondence between a range and a link.
- parameter url: url.
- parameter range: range on which couple url.
*/
func addLink(url url: String, atRange range: NSRange) {
self.links[url] = range
}
// MARK: - Public properties -
/**
Action rised on user interaction on label.
- parameter tapGesture: gesture.
*/
func didTapOnLabel(tapGesture: UITapGestureRecognizer) {
let labelSize = self.bounds.size;
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSizeZero)
let textStorage = NSTextStorage(attributedString: self.attributedText!)
// configure textContainer for the label
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = self.lineBreakMode
textContainer.maximumNumberOfLines = self.numberOfLines
textContainer.size = labelSize;
// configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = tapGesture.locationInView(self)
let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer,
inTextContainer:textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
for (url, value) in self.links {
if let range = value as? NSRange {
if NSLocationInRange(indexOfCharacter, range) {
let url = NSURL(string: url as! String)!
if self.openLinkOnExternalBrowser {
UIApplication.sharedApplication().openURL(url)
}
self.delegate?.tappableLabel?(self, didTapUrl: url, atRange: range)
}
}
}
}
}
将解决方案作为类别 UILabel
(这是您的 UILabel
使用一些属性字符串 NSLinkAttributeName
其中的属性):
@implementation UILabel (Support)
- (BOOL)openTappedLinkAtLocation:(CGPoint)location {
CGSize labelSize = self.bounds.size;
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = self.lineBreakMode;
textContainer.maximumNumberOfLines = self.numberOfLines;
textContainer.size = labelSize;
NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];
NSTextStorage* textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
[textStorage addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, textStorage.length)];
[textStorage addLayoutManager:layoutManager];
CGRect textBoundingBox = [layoutManager usedRectForTextContainer: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(location.x - textContainerOffset.x, location.y - textContainerOffset.y);
NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nullptr];
if (indexOfCharacter >= 0) {
NSURL* url = [textStorage attribute:NSLinkAttributeName atIndex:indexOfCharacter effectiveRange:nullptr];
if (url) {
[[UIApplication sharedApplication] openURL:url];
return YES;
}
}
return NO;
}
@end
- (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange{
NSLayoutManager *layoutManager = [NSLayoutManager new];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = label.lineBreakMode;
textContainer.maximumNumberOfLines = label.numberOfLines;
CGSize labelSize = label.bounds.size;
textContainer.size = labelSize;
CGPoint locationOfTouchInLabel = [self locationInView:label];
CGRect textBoundingBox = [layoutManager usedRectForTextContainer: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);
NSUInteger indexOfCharacter =[layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nil];
return NSLocationInRange(indexOfCharacter, targetRange);
}
NSString *string = name;
NSError *error = NULL;
NSDataDetector *detector =
[NSDataDetector dataDetectorWithTypes:(NSTextCheckingTypes)NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber
error:&error];
NSArray *matches = [detector matchesInString:string
options:0
range:NSMakeRange(0, [string length])];
for (NSTextCheckingResult *match in matches)
{
if (([match resultType] == NSTextCheckingTypePhoneNumber))
{
NSString *phoneNumber = [match phoneNumber];
NSLog(@" Phone Number is :%@",phoneNumber);
label.enabledTextCheckingTypes = NSTextCheckingTypePhoneNumber;
}
if(([match resultType] == NSTextCheckingTypeLink))
{
NSURL *email = [match URL];
NSLog(@"Email is :%@",email);
label.enabledTextCheckingTypes = NSTextCheckingTypeLink;
}
if (([match resultType] == NSTextCheckingTypeLink))
{
NSURL *url = [match URL];
NSLog(@"URL is :%@",url);
label.enabledTextCheckingTypes = NSTextCheckingTypeLink;
}
}
label.text =name;
}