许多应用程序都有文本,文本中是圆角矩形的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
其他回答
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;
}
就像在前面的回答中报告的那样,UITextView能够处理链接上的触摸。这可以通过将文本的其他部分作为链接来轻松扩展。AttributedTextView库是一个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 })
}
- (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);
}
我扩展了@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)
}
}
修改了@timbroder代码,以正确处理swift4.2的多行
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: 0 );
// Adjust for multiple lines of text
let lineModifier = Int(ceil(locationOfTouchInLabel.y / label.font.lineHeight)) - 1
let rightMostFirstLinePoint = CGPoint(x: labelSize.width, y: 0)
let charsPerLine = layoutManager.characterIndex(for: rightMostFirstLinePoint, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let adjustedRange = indexOfCharacter + (lineModifier * charsPerLine)
var newTargetRange = targetRange
if lineModifier > 0 {
newTargetRange.location = targetRange.location+(lineModifier*Int(ceil(locationOfTouchInLabel.y)))
}
return NSLocationInRange(adjustedRange, newTargetRange)
}
}
UILabel代码
let tapAction = UITapGestureRecognizer(target: self, action: #selector(self.tapLabel(gesture:)))
let quote = "For full details please see our privacy policy and cookie policy."
let attributedString = NSMutableAttributedString(string: quote)
let string1: String = "privacy policy", string2: String = "cookie policy"
// privacy policy
let rangeString1 = quote.range(of: string1)!
let indexString1: Int = quote.distance(from: quote.startIndex, to: rangeString1.lowerBound)
attributedString.addAttributes(
[.font: <UIfont>,
.foregroundColor: <UI Color>,
.underlineStyle: 0, .underlineColor:UIColor.clear
], range: NSRange(location: indexString1, length: string1.count));
// cookie policy
let rangeString2 = quote.range(of: string2)!
let indexString2: Int = quote.distance(from: quote.startIndex, to: rangeString2.lowerBound )
attributedString.addAttributes(
[.font: <UIfont>,
.foregroundColor: <UI Color>,
.underlineStyle: 0, .underlineColor:UIColor.clear
], range: NSRange(location: indexString2, length: string2.count));
let label = UILabel()
label.frame = CGRect(x: 20, y: 200, width: 375, height: 100)
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tapAction)
label.attributedText = attributedString
编码来识别水龙头
@objc
func tapLabel(gesture: UITapGestureRecognizer) {
if gesture.didTapAttributedTextInLabel(label: <UILabel>, inRange: termsLabelRange {
print("Terms of service")
} else if gesture.didTapAttributedTextInLabel(label:<UILabel> inRange: privacyPolicyLabelRange) {
print("Privacy policy")
} else {
print("Tapped none")
}
}
推荐文章
- 如何删除默认的导航栏空间在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