UILabel의 NSAttributedString에서 탭 가능한 "링크"를 만드시겠습니까?


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을 가지고 있다면,이 작업은 링크가 표시되는 영역 경계를 하드 코딩하여 비교적 쉽게 해결할 수 있지만,이 문제를보다 우아하고 일반적인 경우-링크 레이아웃에 대한 사전 지식이없는 여러 줄 UILabel을 해결해 보겠습니다.

접근 방식 중 하나는 iOS 7에 도입 된 Text Kit API의 기능을 사용하는 것입니다.

// 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에 둘 이상의 줄이 포함 된 경우이 방법이 제대로 작동하지 않습니다. 누구나 여러 줄로 동일한 작업을 수행하도록 지시 할 수 있습니까
Crazy Developer

1
@CrazyDeveloper 추가 self.textContainer.size = self.label.bounds.size; handleTapOnLabel에서. 그것은 나를 위해 일했다
RadioLog

58

@zekel의 뛰어난 확장 기능 으로 @NAlexN 독창적 인 세부 솔루션을 확장 하고 Swift 에서 제공하고 있습니다.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줄 바꿈 모드가 아닌 경우에만 텍스트가 한 줄이라고 가정합니다.


@ 드리고 루이즈-I은 다음 멀티 라인 일례를 첨가
timbroder

@Koen 여러 개의 링크로 작동합니다. targetRange1and 의 예제 사용법을 참조하십시오 targetRange2.
samwize

2
사람이 여전히 여러 줄 또는 잘못된 범위 문제에 문제가 들어, 귀하의 UILabel의 설정 에 기인 한 후 허용 바꿈을 하고,에 라벨의 속성 첨부 텍스트 설정 NSMutableAttributedString(attributedString: text)경우 '텍스트'입니다NSAttributedString
재정 경제부 Ejegi

@ Mofe-hendyEjegi 여전히 여러 줄 텍스트에 문제가 있습니다. uilabel 너비에 제약 조건이있는 자동 레이아웃을 사용하고 있습니다. 그게 중요할까요?
keno

textAlignment left에 대해 계산 된 값이 작동하지 않으므로 textContainerOffset.x를 수동으로 0으로 설정해야했습니다. 너희들을 위해 일하고 있습니까? 정렬이 중심이면 계산 된 값이 옳다고 생각합니다.
BK

51

오래된 질문이지만 누군가가 UITextView대신 사용할 수 있다면UILabel 쉽습니다. 표준 URL, 전화 번호 등이 자동으로 감지되고 클릭 가능합니다.

그러나 사용자 정의 감지가 필요한 경우, 즉 사용자가 특정 단어를 클릭 한 후 사용자 정의 메소드를 호출하려면 사용자 정의 URL 스킴을 가리키는 속성 NSAttributedStrings과 함께 사용해야 NSLinkAttributeName합니다 ( 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
}

추신 : 물론 당신의 확인 UITextViewIS를 selectable.


수락해야합니다. @NAlexN으로 코드를 가져 와서 UITextView로 5 분 안에 구현하는 데 많은 시간을 보냈습니다.
charlag

문제는 다른 링크에 대해 일반적인 것으로 만들려면 적절한 조치를 취할 URL이 무엇인지 확인해야한다는 것입니다.
hariszaman

33

UIButtonTypeCustom은 이미지를 설정하지 않은 경우 클릭 가능한 레이블입니다.


22
전체 텍스트를 클릭 할 수 있고 링크가 하나만있는 경우에만 가능합니다.
존 팡

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 에이 속성이 없습니까?
매트 볼트

1
@MattBolt 죄송합니다. 실수였습니다. 링크 텍스트의 시작 인덱스 여야합니다 (이 예에서는) plainText.length.
zekel

CGPoint에서 오류가 발생했습니다 locationOfTouchInLabel = [self locationInView : label];
Monika Patel

@zekel이 솔루션에 대해 대단히 감사합니다. 그러나 "UITapGestureRecognizer 범주에 다음 방법을 추가하십시오"라는 말의 의미를 정확히 설명 할 수 있습니까? 내가 여기서 무엇을 해야할지 모르겠습니다.
eivindml

@eivindml 범주를 사용하여 기존 클래스에 메서드를 추가 할 수 있습니다. 이는 작성하지 않은 클래스 (예 :와 같이)에 유용합니다 UITapGestureRecognizer. 카테고리 추가에 대한 정보 는 다음과 같습니다 .
zekel

20

UITextViewOS3.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기능은 @objcSwift 4에 표시되어야합니다 .
peacetype

10

이 게시물 에서 언급했듯이 UILabel FRHyperLabel의 링크를 위해 특별히 만든 경량 라이브러리가 있습니다 .

다음과 같은 효과를 얻으려면

Lorem ipsum의 dolor는 amet, conditetur adipiscing elit에 앉아 있습니다. Pellentesque quis blandit eros는 amet vehicula justo에 앉아 있습니다. urna neque의 남. Maecenas ac sem eu sem portum 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

iOS 7에서 도입 된 textkit API를 기반으로하는 ResponsiveLabel 이라는 UILabel 서브 클래스를 만들었습니다 . 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];

문자열을 클릭 가능하게 만들려면이 방법으로 할 수 있습니다. 이 코드는 각 문자열 "text"에 속성을 적용합니다.

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

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

ResponsiveLabel은 좋은 구성 요소로 보이지만 어떤 이유로 클릭 가능한 텍스트의 색상을 설정할 수 없으며 클릭 가능한 문자열 배열을 설정할 수 없습니다.
Matrosov Alexander 2016

@MatrosovAlexander 현재 ResponsiveLabel에는 문자열 배열을 사용하여 클릭 할 수있는 메소드가 없습니다. github에서 문제를 만들 수 있으며 곧 구현할 것입니다.
hsusmita 2016 년

그러나 문제는 아니지만 배열을 취하는이 방법을 사용하는 것이 좋습니다.
Matrosov Alexander 2016

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을 의미합니다 ;-)
Deny

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



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의 마지막 줄에 등록됩니다.
Christian Schober

1
@ChristianSchober 사용자 정의 글꼴이나 줄 높이가 있습니까?
timbroder

실제로, 우리는 HelveticaNeue 글꼴과 표준 높이를 사용합니다
Christian Schober

1
라벨의 오른쪽 가장자리에 줄 바꿈이 없으면 작동하지 않음
zgjie

기본 글꼴이 있지만 줄 간격이 있고 작동하지 않습니다. 아이디어?
조셉 아스트라한

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 및 범위 목록을 작성했습니다.

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
UIWebView는 할당 할 때 빠르지 않으므로 FancyLabel 또는 TTTAttributedLabel과 같은 UILabel 또는 UITextField 라이브러리를 사용하는 것이 좋습니다. 이것은
테이블 뷰

2

기존 UILabel.attributedText문자열 에서 클릭 가능한 링크를 사용 하여 기존 NSLinkAttributeName속성을 활용 하는 드롭 인 Objective-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 

이것은 UILabel 서브 클래스 (objc_getAssociatedObject 엉망 없음)를 통해 수행되는 약간 더 깔끔하지만, 나와 같은 경우 기존 UIKit 클래스에 추가 기능을 추가하기 위해 불필요한 (서드 파티) 서브 클래스를 만들지 않는 것이 좋습니다. 또한,이은을 클릭 - 링크를 추가하는 아름다움을 가지고 있는 기존의 예 기존의 UILabel의를 UITableViewCells!

NSLinkAttributeNameNSAttributedString에서 이미 사용 가능한 기존 속성 을 사용하여 가능한 한 최소로 침입하려고했습니다 . 따라서 다음과 같이 간단합니다.

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

기본적으로 UIGestureRecognizerUILabel에 a 를 추가하여 작동 합니다. 에서는 어려운 텍스트 작업을 수행하여 gestureRecognizerShouldBegin:밸런싱 된 텍스트 문자열을 다시 배치하여 어떤 문자를 탭했는지 확인합니다. 이 문자가 NSLinkAttributeName의 일부인 경우 gestureRecognizer는 이후에 실행되고 NSLinkAttributeName 값에서 해당 URL을 검색하여 일반적인 [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

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

태그 # 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

swift4.2에 대해 여러 줄을 올바르게 처리하도록 @timbroder 코드를 수정했습니다.

extension UITapGestureRecognizer {

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

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

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

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

        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        let adjustedRange = indexOfCharacter + (lineModifier * charsPerLine)
        var newTargetRange = targetRange
        if lineModifier > 0 {
            newTargetRange.location = targetRange.location+(lineModifier*Int(ceil(locationOfTouchInLabel.y)))
        }
        return NSLocationInRange(adjustedRange, newTargetRange)
    }
}

UILabel 코드

let tapAction = UITapGestureRecognizer(target: self, action: #selector(self.tapLabel(gesture:)))

let quote = "For full details please see our privacy policy and cookie policy."
let attributedString = NSMutableAttributedString(string: quote)

let string1: String = "privacy policy", string2: String = "cookie policy"

// privacy policy
let rangeString1 = quote.range(of: string1)!
let indexString1: Int = quote.distance(from: quote.startIndex, to: rangeString1.lowerBound)
attributedString.addAttributes(
            [.font: <UIfont>,
             .foregroundColor: <UI Color>,
             .underlineStyle: 0, .underlineColor:UIColor.clear
        ], range: NSRange(location: indexString1, length: string1.count));

// cookie policy
let rangeString2 = quote.range(of: string2)!
let indexString2: Int = quote.distance(from: quote.startIndex, to: rangeString2.lowerBound )

attributedString.addAttributes(
            [.font: <UIfont>,
             .foregroundColor: <UI Color>,
             .underlineStyle: 0, .underlineColor:UIColor.clear
        ], range: NSRange(location: indexString2, length: string2.count));

let label = UILabel()
label.frame = CGRect(x: 20, y: 200, width: 375, height: 100)
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tapAction)
label.attributedText = attributedString

탭을 인식하는 코드

 @objc
  func tapLabel(gesture: UITapGestureRecognizer) {
     if gesture.didTapAttributedTextInLabel(label: <UILabel>, inRange: termsLabelRange {
            print("Terms of service")
     } else if gesture.didTapAttributedTextInLabel(label:<UILabel> inRange: privacyPolicyLabelRange) {
            print("Privacy policy")
     } else {
            print("Tapped none")
     }
    }

0

이것은 Kedar의 답변 에 기반한 Xamarin.iOS c # 구현 입니다.

ShouldInteractWithUrl재정의를 통한 MyClickableTextViewWithCustomUrlScheme 구현 :

// 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 #에서 변환 된 objective-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로 설정하면 텍스트 뷰의 높이가 올바르게 조정됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.