许多应用程序都有文本,文本中是圆角矩形的web超链接,当我点击它们时,UIWebView就会打开。让我困惑的是,他们经常有自定义链接,例如,如果单词以#开头,它也是可点击的,应用程序通过打开另一个视图来响应。我该怎么做呢?是否可以用UILabel或者我需要UITextView或者其他什么?
当前回答
从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)
}
其他回答
以下是基于@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)
}
}
}}
(我的回答建立在@NAlexN的精彩回答之上。我不会在这里重复他对每一步的详细解释。)
我发现这是最方便和直接的添加支持可点击的UILabel文本作为类别UITapGestureRecognizer。(你不必使用UITextView的数据检测器,就像一些答案所建议的那样。)
添加以下方法到你的UITapGestureRecognizer类别:
/**
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);
}
Drop-in解决方案作为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
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;
}
从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)
}
推荐文章
- 如何删除默认的导航栏空间在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