许多应用程序都有文本,文本中是圆角矩形的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)
        }

其他回答

我正在扩展@NAlexN原始的详细解决方案,与@zekel优秀的UITapGestureRecognizer扩展,并在Swift中提供。

Extending UITapGestureRecognizer

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才会假定文本为单行。

    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;
}

修改了@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")
     }
    }

翻译@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的示例代码: 来源: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];
}