สร้าง "ลิงก์" แบบแตะได้ใน NSAttributedString ของ UILabel หรือไม่


233

ฉันค้นหาสิ่งนี้มาหลายชั่วโมงแล้ว แต่ล้มเหลว ฉันอาจไม่รู้ด้วยซ้ำว่าฉันควรมองหาอะไร

แอปพลิเคชั่นจำนวนมากมีข้อความและในข้อความนี้เป็นไฮเปอร์ลิงก์ของเว็บในรูปสี่เหลี่ยมมุมฉาก เมื่อฉันคลิกมันจะUIWebViewเปิดขึ้น สิ่งที่ฉันเป็นปริศนาก็คือพวกเขามักจะมีลิงค์ที่กำหนดเองเช่นถ้าคำเริ่มต้นด้วย # มันก็สามารถคลิกได้และแอปพลิเคชันตอบสนองโดยการเปิดมุมมองอื่น ฉันจะทำสิ่งนั้นได้อย่างไร เป็นไปได้ด้วยUILabelหรือฉันต้องการUITextViewหรืออย่างอื่น?


ดู: stackoverflow.com/questions/50505334/…สำหรับวิธีการSwift 4แก้ปัญหาการทำงานอย่างสมบูรณ์ มันใช้แต่ทำให้มันทำตัวเหมือนUITextView UILabelฉันลองวิธีแก้ปัญหาที่นี่และไม่สามารถตรวจสอบลิงก์ที่ถูกต้องได้
Dan Bray

คำตอบ:


208

โดยทั่วไปถ้าเราต้องการให้มีลิงค์ที่สามารถคลิกได้ในข้อความที่แสดงโดย UILabel เราจะต้องแก้ไขงานอิสระสองอย่าง:

  1. การเปลี่ยนลักษณะที่ปรากฏของส่วนของข้อความให้มีลักษณะเหมือนลิงก์
  2. การตรวจจับและจัดการสัมผัสบนลิงค์ (การเปิด URL เป็นกรณีพิเศษ)

คนแรกนั้นง่าย การเริ่มต้นจาก iOS 6 UILabel รองรับการแสดงสตริงที่ประกอบ สิ่งที่คุณต้องทำคือการสร้างและกำหนดค่าอินสแตนซ์ของ NSMutableAttributedString:

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;

แค่นั้นแหละ! รหัสด้านบนทำให้ UILabel แสดงสตริงด้วยลิงก์

ตอนนี้เราควรตรวจจับการสัมผัสที่ลิงค์นี้ แนวคิดก็คือการจับก๊อกทั้งหมดภายใน UILabel และดูว่าตำแหน่งของก๊อกนั้นใกล้กับลิงก์มากพอหรือไม่ เพื่อจับสัมผัสเราสามารถเพิ่มตัวจดจำท่าทางการแตะลงในฉลาก ตรวจสอบให้แน่ใจว่าได้เปิดใช้งาน userInteraction สำหรับป้ายกำกับมันถูกปิดโดยค่าเริ่มต้น:

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

ตอนนี้สิ่งที่ซับซ้อนที่สุด: ค้นหาว่าการแตะอยู่ที่การแสดงลิงก์หรือไม่และไม่ได้อยู่ในส่วนอื่น ๆ ของฉลาก หากเรามี UILabel ที่มีเส้นเดียวงานนี้สามารถแก้ไขได้ค่อนข้างง่ายโดยการเขียนโค้ดขอบเขตพื้นที่ที่ลิงก์ปรากฏ แต่ให้แก้ปัญหานี้ได้ดีกว่าและสำหรับกรณีทั่วไป - multiline UILabel โดยไม่มีความรู้เบื้องต้นเกี่ยวกับโครงร่างลิงค์

หนึ่งในวิธีการคือการใช้ความสามารถของ Text Kit API ที่แนะนำใน 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;

บันทึกอินสแตนซ์ที่สร้างและกำหนดค่าของ NSLayoutManager, NSTextContainer และ NSTextStorage ในคุณสมบัติในคลาสของคุณ (ส่วนใหญ่เป็นลูกหลานของ UIViewController) - เราต้องการพวกมันในวิธีอื่น ๆ

ตอนนี้ทุกครั้งที่ป้ายเปลี่ยนเฟรมให้อัปเดตขนาดของ textContainer:

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

และสุดท้ายตรวจสอบว่าการแตะตรงกับลิงก์:

- (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
ฉันจะจัดระเบียบสิ่งนี้ได้cellForRowAtIndexPathอย่างไร ฉันกำลังสร้างและกำหนดค่าอินสแตนซ์ภายในcellForRowAtIndexPathและโฮสต์handleTapOnLabelฟังก์ชันในนั้นด้วย แต่ที่ฉันได้รับcell.textLabel.addGestureRecognizer(UITapGestureRecognizer(target: cell, action: "handleTapOnLabel:")) unrecognized selector
แถบเลื่อน

13
วิธีการแก้ปัญหานี้อนุมานว่าฉลากแอตทริบิวต์มีการตั้งค่าtextAlignment NSTextAlignmentCenterหากคุณใช้ข้อความที่ไม่อยู่กึ่งกลางคุณจะต้องปรับการคำนวณของคุณtextContainerOffsetในรหัสด้านบน
BradB

18
@AndreyM เมื่อคำนวณxค่าของจะใช้textContainerOffsetค่าคงที่ นี้จะคำนวณตำแหน่งที่ถูกต้องสำหรับ0.5 ไปทางซ้ายชิดธรรมชาติหรือธรรมใช้เป็นค่าNSTextAlignmentCent‌er ไปทางขวาจัดใช้0.0 1.0
BradB

5
มันใช้งานได้สำหรับฉันด้วย แต่สำหรับฉลากบรรทัดเดียวเท่านั้น หาก Label มีมากกว่า 1 บรรทัดแสดงว่าวิธีนี้ใช้ไม่ได้ ทุกคนสามารถบอกให้เขาทำงานเดียวกันหลายบรรทัดได้หรือไม่
Crazy Developer

1
@CrazyDeveloper เพิ่ม self.textContainer.size = self.label.bounds.size; ใน handleTapOnLabel ที่ทำงานให้ฉัน
RadioLog

58

ฉันกำลังขยาย@NAlexNแก้ปัญหารายละเอียดเดิมกับ@zekelส่วนขยายที่ดีของUITapGestureRecognizerและการให้ในสวิฟท์

การขยาย 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โหมดแบ่งบรรทัดจะต้องตั้งค่าให้ตัดคำด้วยคำ / อักขระ อย่างใดNSTextContainerจะถือว่าข้อความเป็นบรรทัดเดียวเท่านั้นหากโหมดการแบ่งบรรทัดเป็นอย่างอื่น


@ rodrigo-ruiz ฉันได้เพิ่มตัวอย่างสำหรับหลายบรรทัดด้านล่าง
timbroder

@Ken มันใช้งานได้กับหลายลิงค์ ดูการใช้งานตัวอย่างเช่นด้วยและtargetRange1 targetRange2
samwize

2
สำหรับทุกคนที่ยังมีปัญหากับหลายบรรทัดหรือปัญหาช่วงไม่ถูกต้องตั้ง UILabel ของคุณเป็นAttributedจากนั้นอนุญาตให้มีการตัดคำและตั้งค่าข้อความประกอบของฉลากNSMutableAttributedString(attributedString: text)ที่ 'ข้อความ' เป็นNSAttributedString
Mofe Ejegi

@ Mofe-hendyEjegi ฉันยังคงมีปัญหากับข้อความหลายบรรทัด ฉันใช้เลย์เอาต์อัตโนมัติที่มีข้อ จำกัด เกี่ยวกับความกว้าง uilabel มันจะสำคัญไหม
keno

ฉันต้องตั้งค่า textContainerOffset.x ด้วยตนเองเป็น 0 เนื่องจากค่าที่คำนวณไม่ทำงานสำหรับ textAlignment left มันทำงานกับพวกคุณเหรอ? ฉันคิดว่าค่าที่คำนวณได้ถูกต้องหากการจัดตำแหน่งเป็นศูนย์กลาง
BK

51

คำถามเก่า แต่ถ้าใครสามารถใช้UITextViewแทนUILabelได้มันง่าย URL มาตรฐานหมายเลขโทรศัพท์ ฯลฯ จะถูกตรวจจับโดยอัตโนมัติ (และสามารถคลิกได้)

อย่างไรก็ตามหากคุณต้องการการตรวจจับที่กำหนดเองนั่นคือถ้าคุณต้องการที่จะเรียกวิธีการที่กำหนดเองใด ๆ หลังจากที่ผู้ใช้คลิกที่คำใดคำหนึ่งคุณจะต้องใช้NSAttributedStringsกับNSLinkAttributeNameแอตทริบิวต์ที่จะชี้ไปที่รูปแบบ URL ที่กำหนดเอง มีรูปแบบ http URL ตามค่าเริ่มต้น) เรย์เวนเดอร์ลิชครอบคลุมที่นี่

การอ้างอิงโค้ดจากลิงก์ข้างต้น:

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;

หากต้องการตรวจสอบการคลิกลิงก์เหล่านี้ให้ใช้สิ่งนี้:

- (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: ให้แน่ใจว่าคุณมีUITextViewselectable


สิ่งนี้ควรได้รับการยอมรับ ฉันใช้เวลาค่อนข้างนานในการพยายามรับรหัสโดย @NAlexN ทำงานแล้วนำไปใช้กับ UITextView ใน 5 นาที
charlag

ปัญหานี้ก็คือถ้าคุณต้องการให้มันทั่วไปสำหรับการเชื่อมโยงที่แตกต่างกันคุณต้องตรวจสอบสิ่งที่อยู่ URL ที่จะดำเนินการที่เหมาะสม
hariszaman

33

UIButtonTypeCustom เป็นป้ายกำกับที่สามารถคลิกได้หากคุณไม่ได้กำหนดภาพใด ๆ


22
เฉพาะเมื่อข้อความทั้งหมดสามารถคลิกได้และลิงก์เดียวเท่านั้น
John Pang

33

(คำตอบของฉันสร้างขึ้นจากคำตอบที่ยอดเยี่ยมของ @ NAlexN ฉันจะไม่ทำซ้ำคำอธิบายโดยละเอียดของเขาในแต่ละขั้นตอนที่นี่)

ฉันพบว่าสะดวกและตรงไปตรงมาที่สุดในการเพิ่มการรองรับข้อความ UILabel แบบแตะได้เป็นหมวดหมู่ใน UITapGestureRecognizer (คุณไม่จำเป็นต้องใช้เครื่องตรวจจับข้อมูลของ UITextView ตามคำแนะนำบางคำแนะนำ)

เพิ่มวิธีการต่อไปนี้ในหมวดหมู่ UITapGestureRecognizer ของคุณ:

/**
 Returns YES if the tap gesture was within the specified range of the attributed text of the label.
 */
- (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange {
    NSParameterAssert(label != nil);

    CGSize labelSize = label.bounds.size;
    // create instances of NSLayoutManager, NSTextContainer and NSTextStorage
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];

    // configure layoutManager and textStorage
    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];

    // configure textContainer for the label
    textContainer.lineFragmentPadding = 0.0;
    textContainer.lineBreakMode = label.lineBreakMode;
    textContainer.maximumNumberOfLines = label.numberOfLines;
    textContainer.size = labelSize;

    // find the tapped character location and compare it to the specified range
    CGPoint locationOfTouchInLabel = [self locationInView:label];
    CGRect textBoundingBox = [layoutManager usedRectForTextContainer:textContainer];
    CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                              (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
    CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                                                         locationOfTouchInLabel.y - textContainerOffset.y);
    NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer
                                                            inTextContainer:textContainer
                                   fractionOfDistanceBetweenInsertionPoints:nil];
    if (NSLocationInRange(indexOfCharacter, targetRange)) {
        return YES;
    } else {
        return NO;
    }
}

รหัสตัวอย่าง

// (in your view controller)    
// create your label, gesture recognizer, attributed text, and get the range of the "link" in your label
myLabel.userInteractionEnabled = YES;
[myLabel addGestureRecognizer:
   [[UITapGestureRecognizer alloc] initWithTarget:self 
                                           action:@selector(handleTapOnLabel:)]]; 

// create your attributed text and keep an ivar of your "link" text range
NSAttributedString *plainText;
NSAttributedString *linkText;
plainText = [[NSMutableAttributedString alloc] initWithString:@"Add label links with UITapGestureRecognizer"
                                                   attributes:nil];
linkText = [[NSMutableAttributedString alloc] initWithString:@" Learn more..."
                                                  attributes:@{
                                                      NSForegroundColorAttributeName:[UIColor blueColor]
                                                  }];
NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] init];
[attrText appendAttributedString:plainText];
[attrText appendAttributedString:linkText];

// ivar -- keep track of the target range so you can compare in the callback
targetRange = NSMakeRange(plainText.length, linkText.length);

ท่าทางการโทรกลับ

// handle the gesture recognizer callback and call the category method
- (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture {
    BOOL didTapLink = [tapGesture didTapAttributedTextInLabel:myLabel
                                            inRange:targetRange];
    NSLog(@"didTapLink: %d", didTapLink);

}

1
เพิ่งจะใช้งานได้ - แต่ฉันมีปัญหากับ linkText.location - NSAttributedString ของฉันไม่มีคุณสมบัตินี้ใช่ไหม
Matt Bolt

1
@ MattBolt อุ๊ปส์นั่นเป็นข้อผิดพลาด plainText.lengthที่ควรจะเป็นดัชนีจุดเริ่มต้นของการเชื่อมโยงข้อความในตัวอย่างนี้มันควรจะเป็น
zekel

เกิดข้อผิดพลาดใน CGPoint locationOfTouchInLabel = [ตนเอง locationInView: label];
Monika Patel

@zekel ขอบคุณมากสำหรับโซลูชั่นนี้ แต่คุณสามารถอธิบายความหมายของคำว่า "เพิ่มวิธีต่อไปนี้ในหมวดหมู่ UITapGestureRecognizer ของคุณ" ได้ไหม? ไม่แน่ใจว่าฉันควรทำอะไรที่นี่
eivindml

@eivindml UITapGestureRecognizerคุณสามารถใช้หมวดหมู่เพื่อเพิ่มวิธีการเรียนที่มีอยู่ซึ่งจะเป็นประโยชน์สำหรับการทำงานกับการเรียนที่คุณไม่ได้เขียนเช่น นี่คือข้อมูลเกี่ยวกับการเพิ่มหมวดหมู่
zekel

20

UITextViewรองรับเครื่องมือตรวจจับข้อมูลใน OS3.0 ในขณะที่UILabelไม่รองรับ

หากคุณเปิดใช้งานเครื่องตรวจจับข้อมูลในUITextViewและข้อความของคุณมี URL หมายเลขโทรศัพท์ ฯลฯ พวกเขาจะปรากฏเป็นลิงก์


ใช่ฉันรู้เกี่ยวกับเรื่องนี้ แต่ฉันต้องการการตรวจสอบยังที่กำหนดเองเช่น #some_word ดังกล่าวในคำถามของฉัน
Lope

@Lope คุณยังสามารถทำเช่นนั้นได้เพียงกำหนดรูปแบบ URL hashtag://หรืออะไรทำนองนั้นจากนั้นใช้textView(_:shouldInteractWith:in:interaction:)เพื่อตรวจจับ ดูคำตอบด้านล่าง: stackoverflow.com/a/34014655/1161906
bcattle

14

การแปลส่วนขยายของ @ 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()
    }
}

6
ไม่ทำงานสำหรับฉัน didTapAttributedTextInLabelต้องการNSRangeข้อโต้แย้ง แต่rangeTermsจะคืนสิ่งที่แตกต่าง นอกจากนี้ยังมีhandleTapOnLabelฟังก์ชั่นควรจะทำเครื่องหมายที่มี@objcในสวิฟท์ 4
peacetype

10

ในฐานะที่ผมกล่าวถึงในโพสต์นี้ที่นี่เป็นห้องสมุดน้ำหนักเบาที่ฉันสร้างขึ้นมาเป็นพิเศษสำหรับการเชื่อมโยงใน UILabel FRHyperLabel

เพื่อให้ได้ผลเช่นนี้:

Lorem ipsum dolor sit amet, consipetur adipiscing elit. Pellentesqueโฟนblanditอยู่, Amet นั่ง vehicula Justo น้ำที่ urna neque Maecenas ac sem eu sem porta dictum nec vel tellus.

ใช้รหัส:

//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
จะทำอย่างไรถ้าข้อความป้ายกำกับเป็นแบบไดนามิกมาจาก API และคุณไม่ทราบความยาวของข้อความวิธีการสร้างลิงก์
Subhash Sharma

ทำงานได้ดีบน Swift 4 เช่นกัน
Hola Soy Edu Feliz Navidad

7

ฉันสร้าง UILabel subclass ชื่อResponsiveLabelซึ่งอยู่บนพื้นฐาน textkit API แนะนำใน iOS 7 จะใช้วิธีการเดียวกันที่แนะนำโดยNAlexN มันมีความยืดหยุ่นในการระบุรูปแบบการค้นหาในข้อความ หนึ่งสามารถระบุรูปแบบที่จะนำไปใช้กับรูปแบบเหล่านั้นเช่นเดียวกับการกระทำที่จะดำเนินการเมื่อแตะที่รูปแบบ

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

หากคุณต้องการให้คลิกเป็นสตริงคุณสามารถทำเช่นนี้ได้ รหัสนี้ใช้คุณลักษณะกับแต่ละสตริง "ข้อความ"

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

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

ResponsiveLabel ดูเหมือนว่าองค์ประกอบที่ดีในการทำงาน แต่ด้วยเหตุผลบางอย่างฉันไม่สามารถตั้งค่าสีสำหรับข้อความที่คลิกได้และไม่สามารถตั้งค่าอาร์เรย์ของสตริงที่คลิกได้
Matrosov Alexander

@MatrosovAlexander ตอนนี้ ResponsiveLabel ไม่มีวิธีที่ใช้อาร์เรย์ของสตริงและทำให้สามารถคลิกได้ คุณสามารถสร้างปัญหาใน GitHub และฉันจะดำเนินการในไม่ช้า
hsusmita

ใช่มันไม่ใช่ปัญหา แต่ดีที่มีวิธีนี้ที่ใช้อาร์เรย์
Matrosov Alexander

6

ทำงานใน Swift 3 แล้ววางโค้ดทั้งหมดที่นี่

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

}

นี่คือ API ใหม่อนุญาตให้ใช้ตั้งแต่ Swift 10 ขึ้นไป :(
t4nhpt

1
@ t4nhpt คุณหมายถึง iOS 10 ;-)
ปฏิเสธ

6

นี่คือตัวอย่างโค้ดสำหรับไฮเปอร์ลิงก์ 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];
}

6

นี่คือคำตอบของ 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)
}
}

จากนั้นคุณสามารถสร้างตัวอย่างของคลาสนั้นในviewDidLoadวิธีการของคุณดังนี้:

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

จะเป็นการดีกว่าหากมีแอตทริบิวต์ที่กำหนดเองที่จะใช้เมื่ออักขระถูกเคาะ ตอนนี้มันเป็นNSLinkAttributeNameแต่อาจเป็นอะไรก็ได้และคุณสามารถใช้ค่านั้นเพื่อทำสิ่งอื่นนอกเหนือจากการเปิด URL คุณสามารถทำการกระทำที่กำหนดเองได้


มันเยี่ยมมาก! ฉันแทนที่ TapGestureRecognizer ด้วย LongPressRecognizer และหยุดการเลื่อนดูตาราง ข้อเสนอแนะใด ๆ สำหรับวิธีป้องกัน gestureRecognizer ไม่ให้เลื่อนการดูตาราง ขอบคุณ !!!
lucius degeer

คุณสามารถใช้ shouldRecognizeSim พร้อมกันdeveloper.apple.com/documentation/uikit/…
mohamede1945

4

ผมมีช่วงเวลาที่ยากที่เกี่ยวข้องกับการนี้ ... UILabel มีการเชื่อมโยงกับมันที่ข้อความประกอบ ... มันเป็นเพียงอาการปวดหัวดังนั้นฉันสิ้นสุดการใช้ZSWTappableLabel


ขอบคุณ มันใช้งานได้จริงในกรณีของฉัน มันจะตรวจสอบรหัสอีเมลหมายเลขโทรศัพท์และลิงค์
Hilaj

4

เช่นเดียวกับที่มีการรายงานไว้ในคำตอบก่อนหน้านี้ UITextView สามารถจัดการกับการแตะลิงก์ได้ สิ่งนี้สามารถขยายได้อย่างง่ายดายโดยการทำให้ส่วนอื่น ๆ ของข้อความทำงานเป็นลิงก์ ไลบรารี AttributedTextView เป็นคลาสย่อย UITextView ที่ทำให้ง่ายต่อการจัดการสิ่งเหล่านี้ สำหรับข้อมูลเพิ่มเติมดูที่: https://github.com/evermeer/AttributedTextView

คุณสามารถทำให้ส่วนใดส่วนหนึ่งของข้อความโต้ตอบเช่นนี้ (โดยที่ textView1 เป็น UITextView IBOutlet):

textView1.attributer =
    "1. ".red
    .append("This is the first test. ").green
    .append("Click on ").black
    .append("evict.nl").makeInteract { _ in
        UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { completed in })
    }.underline
    .append(" for testing links. ").black
    .append("Next test").underline.makeInteract { _ in
        print("NEXT")
    }
    .all.font(UIFont(name: "SourceSansPro-Regular", size: 16))
    .setLinkColor(UIColor.purple) 

และสำหรับการจัดการแฮชแท็กและกล่าวถึงคุณสามารถใช้รหัสดังนี้:

textView1.attributer = "@test: What #hashtags do we have in @evermeer #AtributedTextView library"
    .matchHashtags.underline
    .matchMentions
    .makeInteract { link in
        UIApplication.shared.open(URL(string: "https://twitter.com\(link.replacingOccurrences(of: "@", with: ""))")!, options: [:], completionHandler: { completed in })
    }

3

ฉันกำลังขยายคำตอบของ @ samwize เพื่อจัดการ UILabel หลายบรรทัดและยกตัวอย่างการใช้ UIButton

extension UITapGestureRecognizer {

    func didTapAttributedTextInButton(button: UIButton, inRange targetRange: NSRange) -> Bool {
        guard let label = button.titleLabel else { return false }
        return didTapAttributedTextInLabel(label, inRange: targetRange)
    }

    func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize.zero)
        let textStorage = NSTextStorage(attributedString: label.attributedText!)

        // Configure layoutManager and textStorage
        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        // Configure textContainer
        textContainer.lineFragmentPadding = 0.0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines
        let labelSize = label.bounds.size
        textContainer.size = labelSize

        // Find the tapped character location and compare it to the specified range
        let locationOfTouchInLabel = self.locationInView(label)
        let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
        let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                              (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
        let locationOfTouchInTextContainer = CGPointMake((locationOfTouchInLabel.x - textContainerOffset.x),
                                                         0 );
        // Adjust for multiple lines of text
        let lineModifier = Int(ceil(locationOfTouchInLabel.y / label.font.lineHeight)) - 1
        let rightMostFirstLinePoint = CGPointMake(labelSize.width, 0)
        let charsPerLine = layoutManager.characterIndexForPoint(rightMostFirstLinePoint, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        let adjustedRange = indexOfCharacter + (lineModifier * charsPerLine)

        return NSLocationInRange(adjustedRange, targetRange)
    }

}

ฉันลองวิธีแก้ปัญหาของคุณสำหรับ UILabel หลายบรรทัดและมันไม่ได้ผลสำหรับฉันจริงๆ การสัมผัสนั้นลงทะเบียนในบรรทัดสุดท้ายของ UILabel ของฉันเสมอ
คริสเตียน Schober

1
@ChristianSchober คุณมีแบบอักษรที่กำหนดเองหรือความสูงบรรทัด?
timbroder

ไม่จริงเราใช้แบบอักษร HelveticaNeue และความสูงมาตรฐาน
Christian Schober

1
ไม่ทำงานเมื่อตัวแบ่งบรรทัดไม่อยู่บนขอบด้านขวาของป้ายกำกับ
zgjie

ฉันมีแบบอักษรเริ่มต้น แต่มีช่องว่างระหว่างบรรทัดและไม่ทำงานความคิดใด ๆ
Joseph Astrahan

3

ฉันติดตามรุ่นนี้

สวิฟท์ 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
    }
}

ตัวอย่างการโทร:

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

3

ฉันพบวิธีแก้ไขปัญหาอื่น:

ฉันค้นหาวิธีการตรวจสอบลิงก์ในข้อความ html ที่คุณค้นหาจากอินเทอร์เน็ตที่คุณแปลงเป็น nsattributeString ด้วย:

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
        }

วิธีการของฉันช่วยให้คุณตรวจจับการเชื่อมโยงหลายมิติโดยไม่ต้องระบุ

  • ก่อนอื่นให้คุณสร้างส่วนขยายของ tapgesturerecognizer:

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

    }

ในตัวคุณดูคอนโทรลเลอร์คุณสร้างรายการ url และ range เพื่อเก็บลิงค์ทั้งหมดและช่วงที่ข้อความของ attribute มี

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

เพื่อค้นหา URL และ URLRange คุณสามารถใช้:

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

    }

จากนั้นคุณใช้การแตะที่จับ:

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

และไปเลย!

ฉันหวังว่าโซลูชันนี้จะช่วยคุณได้เช่นนี้ช่วยฉันได้


2

สำหรับลิงก์ที่กำหนดเองโดยสมบูรณ์คุณจะต้องใช้ UIWebView - คุณสามารถสกัดกั้นการโทรออกเพื่อให้คุณสามารถไปที่ส่วนอื่น ๆ ของแอปแทนเมื่อกดลิงก์


3
UIWebViews นั้นไม่เร็วเมื่อจัดสรรดังนั้นการใช้ไลบรารี UILabel หรือ UITextField เช่น FancyLabel หรือ TTTAttributedLabel จะดีกว่าถ้าคุณสามารถออกไปได้ นี่เป็นเรื่องที่เกี่ยวข้องโดยเฉพาะอย่างยิ่งหากคุณต้องการลิงค์ที่สามารถคลิกได้ซึ่งรวมอยู่ในเซลล์ tableview และอื่น ๆ
Niall Mccormack

2

ต่อไปนี้เป็นหมวดหมู่ Objective-C แบบดรอปดาวน์ที่เปิดใช้งานลิงก์ที่คลิกได้ในUILabel.attributedTextสตริงที่มีอยู่โดยใช้ประโยชน์จากNSLinkAttributeNameคุณลักษณะที่มีอยู่

@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 

นี่จะเป็นการทำความสะอาดนิดหน่อยที่ทำผ่านคลาสย่อย UILabel (เช่นไม่มีระเบียบ objc_getAssociatedObject) แต่ถ้าคุณเป็นเหมือนฉันคุณต้องการหลีกเลี่ยงการสร้างคลาสย่อยที่ไม่จำเป็น (บุคคลที่ 3) เพื่อเพิ่มฟังก์ชันพิเศษบางอย่างให้กับคลาส UIKit ที่มีอยู่ นอกจากนี้มีความงามที่จะเพิ่มลิงค์คลิกเพื่อใด ๆ UILabel ที่มีอยู่เช่นที่มีอยู่UITableViewCells!

ฉันพยายามทำให้การบุกรุกน้อยที่สุดเท่าที่จะทำได้โดยใช้NSLinkAttributeNameสิ่งที่มีอยู่ใน NSAttributedString ดังนั้นมันง่ายเหมือน:

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

โดยทั่วไปจะใช้งานได้โดยเพิ่ม a UIGestureRecognizerไปยัง UILabel ของคุณ การทำงานอย่างหนักนั้นเสร็จสิ้นgestureRecognizerShouldBegin:แล้วซึ่งจะจัดรูปแบบสายอักขระข้อความที่เป็นข้อความเพื่อค้นหาว่าอักขระใดถูกแตะอยู่ หากตัวละครนี้เป็นส่วนหนึ่งของ NSLinkAttributeName แล้ว gestureRecognizer จะเริ่มทำงานในภายหลังเรียกคืน URL ที่เกี่ยวข้อง (จากค่า NSLinkAttributeName) และเปิดลิงก์ตาม[UIApplication.sharedApplication openURL:url]กระบวนการปกติ

หมายเหตุ - ด้วยการทำสิ่งนี้ทั้งหมดgestureRecognizerShouldBegin:หากคุณไม่แตะที่ลิงก์ในป้ายกำกับเหตุการณ์จะถูกส่งผ่านไป ตัวอย่างเช่น UITableViewCell ของคุณจะดักจับการเชื่อมโยง แต่อย่างอื่นจะทำงานได้ตามปกติ (เลือกเซลล์, ยกเลิกการเลือก, เลื่อน, ... )

ฉันได้ใส่นี้ในพื้นที่เก็บข้อมูล GitHub ที่นี่ ที่ดัดแปลงมาจากไก่ Burghardt ของการโพสต์ SO ที่นี่


1

สร้างคลาสด้วยไฟล์. h และ. m ต่อไปนี้ ในไฟล์. m มีฟังก์ชั่นดังต่อไปนี้

 - (void)linkAtPoint:(CGPoint)location

ภายในฟังก์ชั่นนี้เราจะตรวจสอบช่วงของสารตั้งต้นที่เราต้องดำเนินการ ใช้ตรรกะของคุณเองเพื่อวางช่วงของคุณ

และต่อไปนี้คือการใช้คลาสย่อย

TaggedLabel *label = [[TaggedLabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:label];
label.numberOfLines = 0;
NSMutableAttributedString *attributtedString = [[NSMutableAttributedString alloc] initWithString : @"My name is @jjpp" attributes : @{ NSFontAttributeName : [UIFont systemFontOfSize:10],}];                                                                                                                                                                              
//Do not forget to add the font attribute.. else it wont work.. it is very important
[attributtedString addAttribute:NSForegroundColorAttributeName
                        value:[UIColor redColor]
                        range:NSMakeRange(11, 5)];//you can give this range inside the .m function mentioned above

ต่อไปนี้เป็นไฟล์. h

#import <UIKit/UIKit.h>

@interface TaggedLabel : UILabel<NSLayoutManagerDelegate>

@property(nonatomic, strong)NSLayoutManager *layoutManager;
@property(nonatomic, strong)NSTextContainer *textContainer;
@property(nonatomic, strong)NSTextStorage *textStorage;
@property(nonatomic, strong)NSArray *tagsArray;
@property(readwrite, copy) tagTapped nameTagTapped;

@end   

ต่อไปนี้เป็นไฟล์. m

#import "TaggedLabel.h"
@implementation TaggedLabel

- (id)initWithFrame:(CGRect)frame
{
 self = [super initWithFrame:frame];
 if (self)
 {
  self.userInteractionEnabled = YES;
 }
return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
 self = [super initWithCoder:aDecoder];
if (self)
{
 self.userInteractionEnabled = YES;
}
return self;
}

- (void)setupTextSystem
{
 _layoutManager = [[NSLayoutManager alloc] init];
 _textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
 _textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
 // Configure layoutManager and textStorage
 [_layoutManager addTextContainer:_textContainer];
 [_textStorage addLayoutManager:_layoutManager];
 // Configure textContainer
 _textContainer.lineFragmentPadding = 0.0;
 _textContainer.lineBreakMode = NSLineBreakByWordWrapping;
 _textContainer.maximumNumberOfLines = 0;
 self.userInteractionEnabled = YES;
 self.textContainer.size = self.bounds.size;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 if (!_layoutManager)
 {
  [self setupTextSystem];
 }
 // Get the info for the touched link if there is one
 CGPoint touchLocation = [[touches anyObject] locationInView:self];
 [self linkAtPoint:touchLocation];
}

- (void)linkAtPoint:(CGPoint)location
{
 // Do nothing if we have no text
 if (_textStorage.string.length == 0)
 {
  return;
 }
 // Work out the offset of the text in the view
 CGPoint textOffset = [self calcGlyphsPositionInView];
 // Get the touch location and use text offset to convert to text cotainer coords
 location.x -= textOffset.x;
 location.y -= textOffset.y;
 NSUInteger touchedChar = [_layoutManager glyphIndexForPoint:location inTextContainer:_textContainer];
 // If the touch is in white space after the last glyph on the line we don't
 // count it as a hit on the text
 NSRange lineRange;
 CGRect lineRect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex:touchedChar effectiveRange:&lineRange];
 if (CGRectContainsPoint(lineRect, location) == NO)
 {
  return;
 }
 // Find the word that was touched and call the detection block
    NSRange range = NSMakeRange(11, 5);//for this example i'm hardcoding the range here. In a real scenario it should be iterated through an array for checking all the ranges
    if ((touchedChar >= range.location) && touchedChar < (range.location + range.length))
    {
     NSLog(@"range-->>%@",self.tagsArray[i][@"range"]);
    }
}

- (CGPoint)calcGlyphsPositionInView
{
 CGPoint textOffset = CGPointZero;
 CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
 textBounds.size.width = ceil(textBounds.size.width);
 textBounds.size.height = ceil(textBounds.size.height);

 if (textBounds.size.height < self.bounds.size.height)
 {
  CGFloat paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2.0;
  textOffset.y = paddingHeight;
 }

 if (textBounds.size.width < self.bounds.size.width)
 {
  CGFloat paddingHeight = (self.bounds.size.width - textBounds.size.width) / 2.0;
  textOffset.x = paddingHeight;
 }
 return textOffset;
 }

@end

1

ฉันขอแนะนำอย่างยิ่งให้ใช้ไลบรารีที่ตรวจจับ URL ในข้อความโดยอัตโนมัติและแปลงเป็นลิงก์ ลอง:

ทั้งสองอยู่ภายใต้ใบอนุญาต MIT


คุณกำลังทำซ้ำคำตอบก่อนหน้า
Cœur

1

จากคำตอบของ Charles Gamble สิ่งที่ฉันใช้ (ฉันลบบางบรรทัดที่ทำให้ฉันสับสนและทำให้ฉันผิดดัชนี):

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

โซลูชันดร็อปอินเป็นหมวดหมู่UILabel(ซึ่งถือว่าคุณUILabelใช้สตริงที่ประกอบกับNSLinkAttributeNameคุณลักษณะบางอย่างในนั้น):

@implementation UILabel (Support)

- (BOOL)openTappedLinkAtLocation:(CGPoint)location {
  CGSize labelSize = self.bounds.size;

  NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
  textContainer.lineFragmentPadding = 0.0;
  textContainer.lineBreakMode = self.lineBreakMode;
  textContainer.maximumNumberOfLines = self.numberOfLines;
  textContainer.size = labelSize;

  NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
  [layoutManager addTextContainer:textContainer];

  NSTextStorage* textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
  [textStorage addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, textStorage.length)];
  [textStorage addLayoutManager:layoutManager];

  CGRect textBoundingBox = [layoutManager usedRectForTextContainer:textContainer];
  CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                            (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
  CGPoint locationOfTouchInTextContainer = CGPointMake(location.x - textContainerOffset.x, location.y - textContainerOffset.y);
  NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nullptr];
  if (indexOfCharacter >= 0) {
    NSURL* url = [textStorage attribute:NSLinkAttributeName atIndex:indexOfCharacter effectiveRange:nullptr];
    if (url) {
      [[UIApplication sharedApplication] openURL:url];
      return YES;
    }
  }
  return NO;
}

@end

1

นี่คือการใช้งาน Swift ที่ใกล้เคียงที่สุดเท่าที่จะเป็นไปได้ซึ่งรวมถึงคำติชมแบบสัมผัส คำเตือน:

  1. คุณต้องตั้งค่าฟอนต์ใน NSAttributedStrings ของคุณ
  2. คุณสามารถใช้ NSAttributedStrings เท่านั้น!
  3. คุณต้องให้แน่ใจว่าการเชื่อมโยงของคุณไม่สามารถห่อ (ใช้พื้นที่ทำลายไม่ใช่: "\u{a0}")
  4. คุณไม่สามารถเปลี่ยน lineBreakMode หรือ numberOfLines หลังจากตั้งค่าข้อความ
  5. คุณสร้างลิงค์โดยการเพิ่มคุณสมบัติด้วย.linkกุญแจ

.

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

วิธีการทั่วไปนี้ก็ใช้ได้เช่นกัน!

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

และคุณสามารถเรียกวิธีด้วย

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

ในตัวอย่างการใช้รหัสของคุณUITapGestureRecognizerมาจากไหน มันเป็นทางออกหรือไม่? คุณสมบัติที่คุณติดตั้ง?
Mark Moeykens

1

นี่คือคำตอบของฉันอยู่บนพื้นฐานของ @Luca DAvanzo ของคำตอบ , แทนที่touchesBeganเหตุการณ์แทนท่าทางประปา:

import UIKit

public protocol TapableLabelDelegate: NSObjectProtocol {
   func tapableLabel(_ label: TapableLabel, didTapUrl url: String, atRange range: NSRange)
}

public class TapableLabel: UILabel {

private var links: [String: NSRange] = [:]
private(set) var layoutManager = NSLayoutManager()
private(set) var textContainer = NSTextContainer(size: CGSize.zero)
private(set) var textStorage = NSTextStorage() {
    didSet {
        textStorage.addLayoutManager(layoutManager)
    }
}

public weak var delegate: TapableLabelDelegate?

public override var attributedText: NSAttributedString? {
    didSet {
        if let attributedText = attributedText {
            textStorage = NSTextStorage(attributedString: attributedText)
        } else {
            textStorage = NSTextStorage()
            links = [:]
        }
    }
}

public override var lineBreakMode: NSLineBreakMode {
    didSet {
        textContainer.lineBreakMode = lineBreakMode
    }
}

public override var numberOfLines: Int {
    didSet {
        textContainer.maximumNumberOfLines = numberOfLines
    }
}


public override init(frame: CGRect) {
    super.init(frame: frame)
    setup()
}

public required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setup()
}

public override func layoutSubviews() {
    super.layoutSubviews()
    textContainer.size = bounds.size
}


/// addLinks
///
/// - Parameters:
///   - text: text of link
///   - url: link url string
public func addLink(_ text: String, withURL url: String) {
    guard let theText = attributedText?.string as? NSString else {
        return
    }

    let range = theText.range(of: text)

    guard range.location !=  NSNotFound else {
        return
    }

    links[url] = range
}

private func setup() {
    isUserInteractionEnabled = true
    layoutManager.addTextContainer(textContainer)
    textContainer.lineFragmentPadding = 0
    textContainer.lineBreakMode = lineBreakMode
    textContainer.maximumNumberOfLines  = numberOfLines
}

public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let locationOfTouch = touches.first?.location(in: self) else {
        return
    }

    textContainer.size = bounds.size
    let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)

    for (urlString, range) in links {
        if NSLocationInRange(indexOfCharacter, range), let url = URL(string: urlString) {
            delegate?.tapableLabel(self, didTapUrl: urlString, atRange: range)
        }
    }
}}

0

TAGS # Swift2.0

ฉันรับแรงบันดาลใจจาก - ยอดเยี่ยม - คำตอบของ @ NAlexN และฉันตัดสินใจที่จะเขียน UILabel ด้วยตัวเอง
ฉันยังลองTTTAttributedLabelแต่ฉันไม่สามารถใช้งานได้

หวังว่าคุณจะสามารถชื่นชมรหัสนี้คำแนะนำใด ๆ ยินดีต้อนรับ!

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

}

ในกรณีของฉันมีผลลัพธ์แปลก ๆ ที่มีเพียงข้อความบรรทัดเดียวในการคำนวณดัชนีของตัวละครมันมักจะส่งคืน0สาเหตุที่locationOfTouchInTextContainer.x เป็นลบ ฉันพยายามใช้let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)แทนและใช้งานได้ดี
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

แก้ไข @timbroder code เพื่อจัดการหลายบรรทัดอย่างถูกต้องสำหรับ 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

รหัสที่ใช้จดจำ 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

นี่คือการดำเนินการ Xamarin.iOS C # อยู่บนพื้นฐานของดาร์ของคำตอบ

MyClickableTextViewWithCustomUrlScheme การใช้งานที่มีการShouldInteractWithUrlแทนที่:

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

การแปลงรหัส -C ใน c # กลายเป็น:

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;

ตรวจสอบให้แน่ใจว่าได้ตั้ง Editable = false และ Selectable = true เพื่อให้สามารถคลิกลิงค์ได้

ScrollEnabled = true ยังอนุญาตให้ textview ปรับขนาดความสูงได้อย่างถูกต้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.