许多应用程序都有文本,文本中是圆角矩形的web超链接,当我点击它们时,UIWebView就会打开。让我困惑的是,他们经常有自定义链接,例如,如果单词以#开头,它也是可点击的,应用程序通过打开另一个视图来响应。我该怎么做呢?是否可以用UILabel或者我需要UITextView或者其他什么?
当前回答
用下面的.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
其他回答
从iOS 15开始,SwiftUI内置支持Markdown标记语言,因此Markdown中的文本:
Text("You can [click this link](https://www.example.com) to visit the website.")
或者作为一个使用SwiftUI的最简单的例子,你可以这样做:
HStack() {
Text("Open the")
.foregroundColor(.black)
Link(destination: URL(string: "https://www.example.com/TOS.html")!) {
Text("link")
.foregroundColor(.blue)
.underline()
}
Text("in browser")
.foregroundColor(.black)
}
下面是超链接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];
}
UITextView支持OS3.0中的数据检测器,而UILabel不支持。
如果你在UITextView上启用了数据检测器,并且你的文本包含url、电话号码等,它们将以链接的形式出现。
我们使用来自zekel的UITapGestureRecognizer类别的方便解决方案。 它使用了NSTextContainer,就像这个问题的许多答案一样。
但是,这将返回错误的字符索引。显然是因为NSTextContainer缺少关于字体样式的信息,正如这些其他帖子所指出的:
https://stackoverflow.com/a/34238382/2439941 https://stackoverflow.com/a/47358270/2439941
后改变:
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
To:
// Apply the font of the label to the attributed text:
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:label.attributedText];
NSMutableParagraphStyle *paragraphStyle = NSMutableParagraphStyle.new;
paragraphStyle.alignment = self.label.textAlignment;
[attributedText addAttributes:@{NSFontAttributeName: label.font, NSParagraphStyleAttributeName: paragraphStyle}
range:NSMakeRange(0, label.attributedText.string.length)];
// Init with attributed text from label:
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
结果明显更好,点击区域现在正确地从目标字符串的第一个字符开始。但最后一个字符仍然返回NO。我们期望这与我们的目标字符串具有将字体权重设置为UIFontWeightSemibold的属性有关。而上面的代码改进应用标签。整个字符串上的字体,它有一个规则的权重。
为了解决这个问题,我们进一步改进了上面的代码片段,通过遍历所有属性范围,以支持文本中的多种字体样式:
// According to https://stackoverflow.com/a/47358270/2439941 it's required to apply the paragraph style and font of the UILabel.
// However, the attributed string might contain font formatting as well, e.g. to emphasize a word in a different font style.
// Therefor copy all attributes:
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:label.attributedText];
[label.attributedText enumerateAttributesInRange:NSMakeRange(0, label.attributedText.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
// Add each attribute:
[attributedText addAttributes:attrs
range:range];
// In case the attributes of this range do NOT contain a font specifier, apply the font from the UILabel:
if (![attrs objectForKey:NSFontAttributeName]) {
[attributedText addAttributes:@{ NSFontAttributeName : label.font }
range:range];
}
}];
// Init the storage with the font attributed text:
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
现在,该方法为半粗体字符串范围内的每个字符返回YES,这是预期的结果。
翻译@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()
}
}
推荐文章
- 如何删除默认的导航栏空间在SwiftUI导航视图
- 如何在iOS中使用Swift编程segue
- Swift -整数转换为小时/分钟/秒
- Swift:声明一个空字典
- 我如何用javascript编程点击链接?
- 在成功提交我的应用程序后,“太多符号文件”
- 首先添加一个UIView,甚至是导航栏
- 我如何改变UIButton标题颜色?
- 在Swift中如何调用GCD主线程上的参数方法?
- NSLayoutConstraints是可动画的吗?
- iOS -构建失败,CocoaPods无法找到头文件
- CFNetwork SSLHandshake iOS 9失败
- 请求失败:不可接受的内容类型:文本/html使用AFNetworking 2.0
- 缺少推荐的图标文件-该包不包含iPhone / iPod Touch的应用程序图标,像素为“120x120”,png格式
- 以编程方式创建segue