Tạo các liên kết có thể gõ vào các ứng dụng trên mạng trong NSAttributionString của một UILabel?


233

Tôi đã tìm kiếm điều này trong nhiều giờ nhưng tôi đã thất bại. Tôi thậm chí có thể không biết những gì tôi nên tìm kiếm.

Nhiều ứng dụng có văn bản và trong văn bản này là các siêu liên kết web trong trực tràng tròn. Khi tôi nhấp vào chúng UIWebViewmở ra. Điều khiến tôi thắc mắc là họ thường có các liên kết tùy chỉnh, ví dụ nếu các từ bắt đầu bằng # thì nó cũng có thể nhấp được và ứng dụng phản hồi bằng cách mở một chế độ xem khác. Làm thế nào tôi có thể làm điều đó? Có thể với UILabelhoặc tôi cần UITextViewhoặc cái gì khác?


Xem: stackoverflow.com/questions/50505334/ cho một Swift 4giải pháp hoạt động hoàn toàn . Nó sử dụng UITextViewnhưng làm cho nó hoạt động như một UILabel. Tôi đã thử các giải pháp ở đây và không thể phát hiện liên kết chính xác.
Dan Bray

Câu trả lời:


208

Nói chung, nếu chúng tôi muốn có một liên kết có thể nhấp trong văn bản được hiển thị bởi UILabel, chúng tôi sẽ cần giải quyết hai tác vụ độc lập:

  1. Thay đổi diện mạo của một phần văn bản để trông giống như một liên kết
  2. Phát hiện và xử lý chạm vào liên kết (mở URL là một trường hợp cụ thể)

Điều đầu tiên là dễ dàng. Bắt đầu từ iOS 6 UILabel hỗ trợ hiển thị các chuỗi được quy. Tất cả những gì bạn cần làm là tạo và cấu hình một thể hiện của NSMutableAttributionString:

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;

Đó là nó! Đoạn mã trên làm cho UILabel hiển thị Chuỗi có liên kết

Bây giờ chúng ta nên phát hiện chạm vào liên kết này. Ý tưởng là bắt tất cả các vòi trong UILabel và tìm hiểu xem vị trí của vòi có đủ gần với liên kết hay không. Để bắt được chạm, chúng ta có thể thêm nhận dạng cử chỉ nhấn vào nhãn. Đảm bảo bật userInteraction cho nhãn, nó được tắt theo mặc định:

label.userInteractionEnabled = YES;
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]]; 

Bây giờ là thứ phức tạp nhất: tìm hiểu xem liệu vòi có ở nơi liên kết được hiển thị và không trên bất kỳ phần nào khác của nhãn. Nếu chúng ta có UILabel một dòng, nhiệm vụ này có thể được giải quyết tương đối dễ dàng bằng cách mã hóa giới hạn khu vực nơi liên kết được hiển thị, nhưng hãy giải quyết vấn đề này một cách thanh lịch hơn và trong trường hợp chung - UILabel đa cấp mà không có kiến ​​thức sơ bộ về bố cục liên kết.

Một trong những cách tiếp cận là sử dụng các khả năng của API Text Kit được giới thiệu trong iOS 7:

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

Lưu các phiên bản được tạo và định cấu hình của NSLayoutManager, NSTextContainer và NSTextStorage trong các thuộc tính trong lớp của bạn (rất có thể là hậu duệ của UIViewContoder) - chúng ta sẽ cần chúng trong các phương thức khác.

Bây giờ, mỗi khi nhãn thay đổi khung của nó, hãy cập nhật kích thước của textContainer:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    self.textContainer.size = self.label.bounds.size;
}

Và cuối cùng, phát hiện xem liệu vòi có chính xác trên liên kết hay không:

- (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/"]];
    }
}

1
Điều này làm việc tuyệt vời! Tuy nhiên, vì tôi sử dụng văn bản được gán trong Nhãn sao cho văn bản xuất hiện là " Peter đã nhắn tin cho Mary và tên có thể nhấp được, nên văn bản khác thì không. Vì vậy, tôi đã trộn mã từ liên kết . Ngoài ra, trộn với NSAttributionString + CCLFormat để văn bản của tôi có thể được bản địa hóa theo thứ tự khác nhau.
John Pang

1
Làm thế nào tôi có thể tổ chức này trong cellForRowAtIndexPath? Tôi cũng đang tạo và định cấu hình các cá thể bên trong cellForRowAtIndexPathvà lưu trữ handleTapOnLabelchức năng trong đó. Nhưng tại cell.textLabel.addGestureRecognizer(UITapGestureRecognizer(target: cell, action: "handleTapOnLabel:")), tôi đang nhận được unrecognized selector.
thanh trượt

13
Giải pháp này giả định rằng textAlignmentthuộc tính của nhãn được đặt thành NSTextAlignmentCenter. Nếu bạn đang sử dụng văn bản không tập trung, bạn sẽ cần điều chỉnh tính toán của bạn textContainerOffsettrong mã trên.
BradB

18
@AndreyM. Khi tính xgiá trị của textContainerOffset, hằng số 0.5được sử dụng. Điều này sẽ tính toán vị trí chính xác cho NSTextAlignmentCent‌er. Để căn trái, tự nhiên hoặc hợp lý, sử dụng giá trị của 0.0. Để căn chỉnh đúng, sử dụng 1.0.
BradB

5
Nó cũng hoạt động với tôi, nhưng chỉ cho một dòng nhãn. Nếu Nhãn chứa nhiều hơn 1 dòng thì phương pháp này không hoạt động đúng. Bất cứ ai cũng có thể bảo anh ta thực hiện cùng một nhiệm vụ với nhiều dòng
Crazy Developer

58

Tôi đang mở rộng giải pháp chi tiết ban đầu @NAlexN , với phần mở rộng tuyệt vời của @zekelUITapGestureRecognizer và cung cấp trong Swift .

Mở rộng 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)
    }

}

Sử dụng

Thiết lập UIGestureRecognizerđể gửi hành động đến tapLabel:và bạn có thể phát hiện nếu phạm vi mục tiêu đang được khai thác 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")
    }
}

QUAN TRỌNG: UILabelChế độ ngắt dòng phải được đặt thành bọc theo từ / char. Bằng cách nào đó, NSTextContainersẽ cho rằng văn bản chỉ là một dòng nếu chế độ ngắt dòng là khác.


@ Rodrigo-ruiz Tôi đã thêm một ví dụ cho nhiều dòng bên dưới
đếm thời gian

@Koen Nó hoạt động với nhiều liên kết. Xem cách sử dụng cho ví dụ với targetRange1targetRange2.
lấy mẫu

2
Đối với bất kỳ ai vẫn gặp sự cố với nhiều dòng hoặc vấn đề phạm vi không chính xác, hãy đặt UILabel của bạn thành Thuộc tính , sau đó cho phép bọc từ và đặt văn bản được gán cho nhãn thành NSMutableAttributedString(attributedString: text)'văn bản' làNSAttributedString
Mofe Ejegi

@ Mofe-hendyEjegi Tôi vẫn gặp sự cố với văn bản nhiều dòng. Tôi đang sử dụng bố cục tự động với các ràng buộc về chiều rộng của uilabel. Điều đó có quan trọng không?
keno

Tôi đã phải đặt thủ công textContainer Offerset.x thành 0, vì giá trị tính toán không hoạt động đối với textAlocation còn lại. Nó có làm việc cho các bạn không? Tôi nghĩ rằng giá trị tính toán là đúng nếu căn chỉnh là trung tâm.
BK

51

Câu hỏi cũ nhưng nếu ai đó có thể sử dụng UITextViewthay vì a UILabel, thì thật dễ dàng. URL tiêu chuẩn, số điện thoại, vv sẽ được tự động phát hiện (và có thể nhấp).

Tuy nhiên, nếu bạn cần phát hiện tùy chỉnh, nghĩa là, nếu bạn muốn có thể gọi bất kỳ phương thức tùy chỉnh nào sau khi người dùng nhấp vào một từ cụ thể, bạn cần sử dụng NSAttributedStringsvới một NSLinkAttributeNamethuộc tính sẽ trỏ đến lược đồ URL tùy chỉnh (trái ngược với có lược đồ url http theo mặc định). Ray Wenderlich có nó ở đây

Trích dẫn mã từ liên kết đã nói ở trên:

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;

Để phát hiện các nhấp chuột liên kết đó, hãy thực hiện điều này:

- (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: Hãy chắc chắn rằng bạn UITextViewselectable.


Điều này nên được chấp nhận. Tôi đã dành khá nhiều thời gian để cố gắng lấy mã bằng cách @NAlexN làm việc và sau đó triển khai nó với UITextView trong 5 phút.
charlag

Vấn đề với điều này là nếu bạn muốn đặt nó chung cho các liên kết khác nhau, bạn phải kiểm tra URL để thực hiện hành động thích hợp
hariszaman

33

UIButtonTypeCustom là nhãn có thể nhấp nếu bạn không đặt bất kỳ hình ảnh nào cho nó.


22
Chỉ khi toàn bộ văn bản có thể nhấp và chỉ có một liên kết.
John Pang

33

(Câu trả lời của tôi dựa trên câu trả lời tuyệt vời của @ NAlexN . Tôi sẽ không lặp lại lời giải thích chi tiết của anh ấy về từng bước ở đây.)

Tôi thấy thuận tiện và đơn giản nhất khi thêm hỗ trợ cho văn bản UILabel có thể nhấn làm danh mục cho UITapGestureRecognizer. (Bạn không phải sử dụng trình phát hiện dữ liệu của UITextView, như một số câu trả lời gợi ý.)

Thêm phương thức sau vào danh mục UITapGestureRecognizer của bạn:

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

Mã ví dụ

// (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);

Gọi lại cử chỉ

// 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);

}

1
chỉ cần làm việc này - nhưng tôi gặp sự cố với linkText.location - NSAttributionString của tôi không có thuộc tính này?
Matt Bolt

1
@MattBolt Rất tiếc, đó là một sai lầm. Đó phải là chỉ mục bắt đầu của văn bản liên kết, trong ví dụ này nó phải như vậy plainText.length.
zekel

Đã xảy ra lỗi trong CGPoint locationOfTouchInLabel = [self locationInView: nhãn];
Monika Patel

@zekel Cảm ơn bạn rất nhiều cho giải pháp này. Nhưng bạn có thể giải thích chính xác ý của bạn bằng cách "Thêm phương thức sau vào danh mục UITapGestureRecognizer" của bạn không? Không chắc chắn những gì tôi nên làm ở đây.
eivindml

@eivindml Bạn có thể sử dụng các danh mục để thêm các phương thức vào các lớp hiện có, rất hữu ích khi làm việc với các lớp bạn không viết, chẳng hạn như UITapGestureRecognizer. Dưới đây là một số thông tin về việc thêm danh mục.
zekel

20

UITextViewhỗ trợ trình phát hiện dữ liệu trong OS3.0, trong khi UILabelkhông.

Nếu bạn bật trình phát hiện dữ liệu trên UITextViewvà văn bản của bạn có chứa URL, số điện thoại, v.v. chúng sẽ xuất hiện dưới dạng liên kết.


vâng, tôi biết về điều này, nhưng tôi cũng cần phát hiện tùy chỉnh, ví dụ #some_word như được đề cập trong câu hỏi của tôi
Lope

@Lope Bạn vẫn có thể làm điều đó, chỉ cần gán cho họ một lược đồ url tùy chỉnh hashtag://hoặc một cái gì đó, sau đó sử dụng textView(_:shouldInteractWith:in:interaction:)để phát hiện nó. Xem câu trả lời dưới đây: stackoverflow.com/a
432014655/1161906

14

Dịch phần mở rộng của @ samwize sang 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)
    }
}

Để thiết lập trình nhận dạng (một khi bạn tô màu văn bản và nội dung):

lblTermsOfUse.isUserInteractionEnabled = true
lblTermsOfUse.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOnLabel(_:))))

... Sau đó, nhận dạng cử chỉ:

@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()
    }
}

6
Không làm việc cho tôi. didTapAttributedTextInLabelcần một NSRangeđối số nhưng rangeTermstrả về một cái gì đó khác nhau. Ngoài ra handleTapOnLabelchức năng phải được đánh dấu bằng @objcSwift 4.
peacetype

10

Như tôi đã đề cập trong bài đăng này , đây là một thư viện có trọng lượng nhẹ mà tôi đã tạo đặc biệt cho các liên kết trong UILabel FRHyperLabel .

Để đạt được hiệu ứng như thế này:

Lorem ipsum dolor ngồi amet, consitetur adipiscing elit. Pellentesque quis blandit eros, ngồi Amet vehicula Justo. Nam tại urna neque. Maecenas ac sem eu sem porta dictum nec vel Tellus.

sử dụng mã:

//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];

1
Điều gì xảy ra nếu văn bản nhãn là động đến từ API và bạn không biết độ dài văn bản thì làm thế nào để tạo liên kết.
Subhash Sharma

Hoạt động tốt trên Swift 4 là tốt.
Hola Soy Edu Feliz Navidad

7

Tôi đã tạo lớp con UILabel có tên ResponsiveLabel dựa trên API bộ văn bản được giới thiệu trong iOS 7. Nó sử dụng cùng một cách tiếp cận được đề xuất bởi NAlexN . Nó cung cấp sự linh hoạt để chỉ định một mẫu để tìm kiếm trong văn bản. Người ta có thể chỉ định các kiểu được áp dụng cho các mẫu đó cũng như hành động được thực hiện khi nhấn vào các mẫu.

//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];

Nếu bạn muốn tạo một chuỗi có thể nhấp, bạn có thể làm theo cách này. Mã này áp dụng các thuộc tính cho mỗi lần xuất hiện của chuỗi "văn bản".

PatternTapResponder tapResponder = ^(NSString *string) {
    NSLog(@"tapped = %@",string);
};

[self.customLabel enableStringDetection:@"text" withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],
                                                                 RLTapResponderAttributeName: tapResponder}];

ResponsiveLabel có vẻ như là các thành phần tốt để làm việc, nhưng vì một số lý do tôi không thể đặt màu cho văn bản có thể nhấp và không thể đặt mảng các chuỗi có thể nhấp.
Matrosov Alexander

@MatrosovAlexander Ngay bây giờ, ResponsiveLabel không có phương thức lấy một chuỗi chuỗi và làm cho chúng có thể nhấp được. Bạn có thể tạo một vấn đề trên github và tôi sẽ sớm thực hiện điều này.
hsusmita

Phải, đó không phải là một vấn đề, nhưng thật tốt khi có phương pháp này có mảng.
Matrosov Alexander

6

Đã làm việc trong Swift 3, dán toàn bộ mã ở đây

    //****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
    }

}

Đây là API mới, chỉ cho phép từ Swift 10 trở lên :(
t4nhpt

1
@ t4nhpt Ý bạn là iOS 10 ;-)
Từ chối

6

Dưới đây là mã ví dụ cho siêu liên kết UILabel: Nguồn: 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];
}

6

Đây là phiên bản nhanh chóng của câu trả lời của 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)
}
}

Sau đó, bạn có thể tạo một thể hiện của lớp đó bên trong viewDidLoadphương thức của bạn như thế này:

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

Tốt hơn là nên có một thuộc tính tùy chỉnh để sử dụng khi gõ một ký tự. Bây giờ, đó là NSLinkAttributeName, nhưng có thể là bất cứ điều gì và bạn có thể sử dụng giá trị đó để làm những việc khác ngoài mở url, bạn có thể thực hiện bất kỳ hành động tùy chỉnh nào.


Điều đó thật tuyệt! Tôi đã thay thế TapGestureRecognizer bằng LongPressRecognizer và nó phá vỡ cuộn màn hình. Bất kỳ đề xuất nào về cách ngăn chặn cử chỉ nhận dạng khỏi cuộn cuộn xem bảng? Cảm ơn!!!
lucius degeer

Bạn có thể sử dụng nênRecognizeSim đồng thời developer.apple.com/documentation/uikit/iêu
mohamede1945

4

Tôi đã có một thời gian khó khăn để xử lý vấn đề này ... UILabel với các liên kết trên nó trên văn bản được quy kết ... nó chỉ là một vấn đề đau đầu nên cuối cùng tôi đã sử dụng ZSWTappableLabel .


Cảm ơn. Nó thực sự hoạt động trong trường hợp của tôi. Nó sẽ phát hiện id email, số điện thoại và liên kết.
Hilaj

4

Giống như đã được báo cáo trong câu trả lời trước đó, UITextView có thể xử lý các chạm trên các liên kết. Điều này có thể dễ dàng được mở rộng bằng cách làm cho các phần khác của văn bản hoạt động như các liên kết. Thư viện AttributionTextView là một lớp con UITextView giúp cho việc xử lý chúng rất dễ dàng. Để biết thêm thông tin, hãy xem: https://github.com/evermeer/AttributionTextView

Bạn có thể làm cho bất kỳ phần nào của văn bản tương tác như thế này (trong đó textView1 là 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) 

Và để xử lý hashtags và đề cập, bạn có thể sử dụng mã như thế này:

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

3

Tôi đang mở rộng câu trả lời của @ samwize để xử lý UILabel nhiều dòng và đưa ra một ví dụ về việc sử dụng cho 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)
    }

}

Tôi đã thử giải pháp của bạn cho một UILabel nhiều dòng và nó không thực sự hiệu quả với tôi. Cảm ứng luôn được đăng ký trong dòng cuối cùng của UILabel của tôi.
Christian Schober

1
@ChristianSchober bạn có phông chữ tùy chỉnh hoặc chiều cao dòng không?
hồ bấm giờ

không thực sự, chúng tôi sử dụng phông chữ HelveticaNeue và chiều cao tiêu chuẩn
Christian Schober

1
không hoạt động khi ngắt dòng không ở cạnh phải của nhãn
zgjie

Tôi có phông chữ mặc định nhưng khoảng cách dòng và không hoạt động, có ý tưởng nào không?
Joseph Astrahan

3

Tôi theo phiên bản này,

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

Ví dụ cuộc gọi:

button.setLinkedTextWithHandler(text: "This website (stackoverflow.com) is awesome", link: "stackoverflow.com") 
{
    // show popup or open to link
}

3

Tôi tìm thấy một giải pháp khác:

Tôi tìm cách phát hiện liên kết trong văn bản html mà bạn tìm thấy từ internet mà bạn chuyển đổi nó thành nsattributionString với:

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
        }

Phương pháp của tôi cho phép bạn phát hiện siêu liên kết mà không phải chỉ định chúng.

  • Đầu tiên, bạn tạo một phần mở rộng của trình nhận dạng tapferurececizer:

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

    }

sau đó trong trình xem của bạn, bạn đã tạo một danh sách các url và phạm vi để lưu trữ tất cả các liên kết và phạm vi mà văn bản thuộc tính chứa:

var listurl : [String] = []
    var listURLRange : [NSRange] = []

để tìm URL và URLRange bạn có thể sử dụng:

    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(_:))))

    }

sau đó bạn thực hiện chạm tay cầm:

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

và chúng ta đi đây!

Tôi hy vọng giải pháp này giúp bạn thích nó giúp tôi.


2

Đối với các liên kết hoàn toàn tùy chỉnh, bạn sẽ cần sử dụng UIWebView - bạn có thể chặn các cuộc gọi, để bạn có thể đi đến một số phần khác của ứng dụng thay vì khi nhấn liên kết.


3
UIWebViews không nhanh như vậy khi phân bổ, vì vậy sử dụng thư viện UILabel hoặc UITextField như FancyLabel hoặc TTTAttributionLabel sẽ tốt hơn nếu bạn có thể thoát khỏi nó. Điều này đặc biệt thích hợp nếu bạn cần các liên kết có thể nhấp được kết hợp trong các ô xem bảng, v.v.
Niall Mccormack

2

Đây là danh mục Objective-C thả xuống cho phép các liên kết có thể nhấp trong các UILabel.attributedTextchuỗi hiện có , khai thác NSLinkAttributeNamethuộc tính hiện có .

@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 

Điều này sẽ được thực hiện sạch hơn một chút thông qua lớp con UILabel (tức là không có mớ hỗn độn objc_getAssociatedObject), nhưng nếu bạn giống tôi, bạn muốn tránh phải tạo các lớp con (bên thứ 3) không cần thiết chỉ để thêm một số chức năng bổ sung vào các lớp UIKit hiện có. Ngoài ra, điều này có vẻ đẹp là nó thêm các liên kết có thể nhấp vào bất kỳ UILabel hiện tại, ví dụ như hiện có UITableViewCells!

Tôi đã cố gắng làm cho nó xâm lấn tối thiểu nhất có thể bằng cách sử dụng hiện có NSLinkAttributeName công cụ thuộc tính đã có trong NSAttributionString. Vì vậy, nó đơn giản như:

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! :-)

Về cơ bản, nó hoạt động bằng cách thêm một UIGestureRecognizer UILabel của bạn. Công việc khó khăn được thực hiện gestureRecognizerShouldBegin:, trong đó bố trí lại chuỗi Attribution để tìm ra ký tự nào được gõ vào. Nếu ký tự này là một phần của NSLinkAttributionName thì cử chỉ nhận dạng sau đó sẽ kích hoạt, truy xuất URL tương ứng (từ giá trị NSLinkAttributionName) và mở liên kết theo [UIApplication.sharedApplication openURL:url]quy trình thông thường .

Lưu ý - bằng cách thực hiện tất cả điều này trong gestureRecognizerShouldBegin: , nếu bạn không tình cờ nhấn vào một liên kết trong nhãn, sự kiện sẽ được thông qua. Vì vậy, ví dụ, UITableViewCell của bạn sẽ ghi lại các vòi trên các liên kết, nhưng nếu không thì sẽ hoạt động bình thường (chọn ô, bỏ chọn, cuộn, ...).

Tôi đã đặt cái này trong kho GitHub ở đây . Chuyển thể từ bài đăng SO của Kai Burghardt tại đây .


1

Tạo lớp với các tệp .h và .m sau đây. Trong tệp .m có chức năng sau

 - (void)linkAtPoint:(CGPoint)location

Bên trong chức năng này, chúng tôi sẽ kiểm tra phạm vi của các chuỗi con mà chúng ta cần đưa ra hành động. Sử dụng logic của riêng bạn để đặt phạm vi của bạn.

Và sau đây là cách sử dụng của lớp con

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

sau đây là tập tin .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   

sau đây là tập tin .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


1

dựa trên câu trả lời của Charles Gamble, đây là những gì tôi đã sử dụng (tôi đã xóa một số dòng làm tôi bối rối và cho tôi lập chỉ mục sai):

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

1

Giải pháp thả vào như một danh mục trên UILabel(điều này giả sử bạn UILabelsử dụng một chuỗi được NSLinkAttributeNamequy cho một số thuộc tính trong đó):

@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

1

Đây là một triển khai Swift tối thiểu nhất có thể, bao gồm cả phản hồi cảm ứng. Hãy cẩn thận:

  1. Bạn phải đặt phông chữ trong NSAttributionStrings của bạn
  2. Bạn chỉ có thể sử dụng NSAttributionStrings!
  3. Bạn phải đảm bảo các liên kết của bạn không thể ngắt (sử dụng khoảng trắng không phá vỡ: "\u{a0}" :)
  4. Bạn không thể thay đổi lineBreakMode hoặc numberOfLines sau khi đặt văn bản
  5. Bạn tạo liên kết bằng cách thêm thuộc tính bằng .linkkhóa

.

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

1

Phương pháp chung này hoạt động quá!

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

Và bạn có thể gọi phương thức với

let text = yourLabel.text
let termsRange = (text as NSString).range(of: fullString)
if yourLabel.didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange: termsRange) {
            showCorrespondingViewController()
        }

Trong ví dụ của bạn về việc sử dụng mã của bạn, nó UITapGestureRecognizerđến từ đâu? Có phải là một lối thoát? Một tài sản bạn thiết lập?
Mark Moeykens

1

Đây là câu trả lời của tôi dựa trên câu trả lời của @Luca Davanzo , ghi đè touchesBegansự kiện thay vì cử chỉ nhấn:

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

0

TAGS # Swift2.0

Tôi lấy cảm hứng từ - xuất sắc - câu trả lời của @ NAlexN và tôi quyết định tự mình viết một gói UILabel.
Tôi cũng đã thử TTTAttributionLabel nhưng tôi không thể làm cho nó hoạt động.

Hy vọng bạn có thể đánh giá cao mã này, bất kỳ đề xuất đều được chào đón!

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

}

Trong trường hợp của tôi, có một kết quả kỳ lạ chỉ với một dòng văn bản để tính chỉ số của ký tự, nó luôn trả về 0nguyên nhân đó locationOfTouchInTextContainer.x là âm. Tôi cố gắng sử dụng let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)thay thế, và hoạt động tốt.
HamGuy

0
- (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);
}

0

Mã @timbroder đã sửa đổi để xử lý chính xác nhiều dòng cho 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)
    }
}

Mã 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

Mã để nhận biết Tap

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

0

Đây là cách triển khai c # Xamarin.iOS dựa trên câu trả lời của Kedar .

Triển khai MyClickableTextViewWithCustomUrlScheme với ShouldInteractWithUrlghi đè:

// Inspired from https://stackoverflow.com/a/44112932/15186
internal class MyClickableTextViewWithCustomUrlScheme : UITextView, IUITextViewDelegate
{
    public MyClickableTextViewWithCustomUrlScheme()
    {
        Initialize();
    }

    public MyClickableTextViewWithCustomUrlScheme(Foundation.NSCoder coder) : base(coder)
    {
        Initialize();
    }

    public MyClickableTextViewWithCustomUrlScheme(Foundation.NSObjectFlag t) : base(t)
    {
        Initialize();
    }

    public MyClickableTextViewWithCustomUrlScheme(IntPtr handle) : base(handle)
    {
        Initialize();
    }

    public MyClickableTextViewWithCustomUrlScheme(CoreGraphics.CGRect frame) : base(frame)
    {
        Initialize();
    }

    public MyClickableTextViewWithCustomUrlScheme(CoreGraphics.CGRect frame, NSTextContainer textContainer) : base(frame, textContainer)
    {
        Initialize();
    }

    void Initialize()
    {
        Delegate = this;
    }

    [Export("textView:shouldInteractWithURL:inRange:")]
    public new bool ShouldInteractWithUrl(UITextView textView, NSUrl URL, NSRange characterRange)
    {
        if (URL.Scheme.CompareTo(@"username") == 0)
        {
            // Launch the Activity
            return false;
        }
        // The system will handle the URL
        return base.ShouldInteractWithUrl(textView, URL, characterRange);
    }
}

Mã mục tiêu-C đã chuyển đổi trong c # trở thành:

MyClickableTextViewWithCustomUrlScheme uiHabitTile = new MyClickableTextViewWithCustomUrlScheme();
uiHabitTile.Selectable = true;
uiHabitTile.ScrollEnabled = false;
uiHabitTile.Editable = false;

// https://stackoverflow.com/a/34014655/15186
string wholeTitle = @"This is an example by marcelofabri";

NSMutableAttributedString attributedString = new NSMutableAttributedString(wholeTitle);
attributedString.AddAttribute(UIStringAttributeKey.Link,
   new NSString("username://marcelofabri"),
   attributedString.Value.RangeOfString(@"marcelofabri")
);
NSMutableDictionary<NSString, NSObject> linkAttributes = new NSMutableDictionary<NSString, NSObject>();
linkAttributes[UIStringAttributeKey.ForegroundColor] = UIColor.Green;
linkAttributes[UIStringAttributeKey.UnderlineColor] = UIColor.LightGray;
linkAttributes[UIStringAttributeKey.UnderlineStyle] = new NSNumber((short)NSUnderlineStyle.PatternSolid);

uiHabitTile.AttributedText = attributedString;

Đảm bảo đặt Editable = false và Selectable = true để có thể nhấp vào liên kết.

Ngoài ra ScrollEnables = true cho phép textview kích thước chính xác chiều cao của nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.