在iOS中检测UITextView中的属性文本的点击

我有一个显示NSAttributedString的UITextView。 这个string包含我想要做出的可点击的单词,这样当它们被点击时,我会被回叫,以便我可以执行一个动作。 我意识到,UITextView可以检测URL上的水龙头,并回电我的委托,但这些不是URL。

在我看来,iOS7和TextKit的力量现在应该是可能的,但是我找不到任何例子,我不知道从哪里开始。

我明白,现在可以在string中创build自定义属性(虽然我还没有这样做),也许这些将有助于检测是否有一个魔术字已被挖掘? 在任何情况下,我仍然不知道如何拦截水龙头,并检测水龙头发生在哪个词上。

请注意,iOS 6兼容性不是必需的。

我只是想帮助别人。 从Shmidt的回答中可以看出,我完全可以像我在原来的问题中所做的那样做。

1)创build一个具有应用于可点击单词的自定义属性的属性string。 例如。

NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a clickable word" attributes:@{ @"myCustomTag" : @(YES) }]; [paragraph appendAttributedString:attributedString]; 

2)创build一个UITextView来显示该string,并添加一个UITapGestureRecognizer。 然后处理水龙头:

 - (void)textTapped:(UITapGestureRecognizer *)recognizer { UITextView *textView = (UITextView *)recognizer.view; // Location of the tap in text-container coordinates NSLayoutManager *layoutManager = textView.layoutManager; CGPoint location = [recognizer locationInView:textView]; location.x -= textView.textContainerInset.left; location.y -= textView.textContainerInset.top; // Find the character that's been tapped on NSUInteger characterIndex; characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:textView.textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; if (characterIndex < textView.textStorage.length) { NSRange range; id value = [textView.attributedText attribute:@"myCustomTag" atIndex:characterIndex effectiveRange:&range]; // Handle as required... NSLog(@"%@, %d, %d", value, range.location, range.length); } } 

当你知道如何很容易!

更新了Swift 3

用Swift检测属性文本的轻敲

有时候对于初学者来说,知道如何去设置它(这对我来说无论如何)是有点困难的,所以这个例子更全面一些,并且使用了Swift 3。

UITextView添加到您的项目。

在这里输入图像描述

设置

在“属性”检查器中使用以下设置:

在这里输入图像描述

在这里输入图像描述

出口

使用名为textView的sockets将UITextView连接到ViewController

将代码添加到您的视图控制器来检测水龙头。 请注意UIGestureRecognizerDelegate

 import UIKit class ViewController: UIViewController, UIGestureRecognizerDelegate { @IBOutlet weak var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() // Create an attributed string let myString = NSMutableAttributedString(string: "Swift attributed text") // Set an attribute on part of the string let myRange = NSRange(location: 0, length: 5) // range of "Swift" let myCustomAttribute = [ "MyCustomAttributeName": "some value"] myString.addAttributes(myCustomAttribute, range: myRange) textView.attributedText = myString // Add tap gesture recognizer to Text View let tap = UITapGestureRecognizer(target: self, action: #selector(myMethodToHandleTap(_:))) tap.delegate = self textView.addGestureRecognizer(tap) } func myMethodToHandleTap(_ sender: UITapGestureRecognizer) { let myTextView = sender.view as! UITextView let layoutManager = myTextView.layoutManager // location of tap in myTextView coordinates and taking the inset into account var location = sender.location(in: myTextView) location.x -= myTextView.textContainerInset.left; location.y -= myTextView.textContainerInset.top; // character index at tap location let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil) // if index is valid then do something. if characterIndex < myTextView.textStorage.length { // print the character index print("character index: \(characterIndex)") // print the character at the index let myRange = NSRange(location: characterIndex, length: 1) let substring = (myTextView.attributedText.string as NSString).substring(with: myRange) print("character at index: \(substring)") // check if the tap location has a certain attribute let attributeName = "MyCustomAttributeName" let attributeValue = myTextView.attributedText.attribute(attributeName, at: characterIndex, effectiveRange: nil) as? String if let value = attributeValue { print("You tapped on \(attributeName) and the value is: \(value)") } } } } 

在这里输入图像描述

现在,如果你点击“Swift”的“w”,你应该得到以下结果:

在这里输入图像描述

笔记

  • 在这里我使用了一个自定义的属性,但是它可以很容易地被NSForegroundColorAttributeName (文本颜色),它的值为UIColor.greenColor()
  • 这仅适用于文本视图设置为不可编辑且不可选的情况,如上面的“设置”部分所述。 使其可编辑和可选是在下面的评论中讨论的问题的原因。

进一步研究

这个答案是基于这个问题的其他几个答案。 除此之外,另请参阅

  • 高级文本布局和文本工具包的效果(WWDC 2013video)
  • “属性string编程指南”
  • 如何使用Swift创build一个属性string?

这是一个稍微修改的版本,build立@tarmes的答案。 我无法得到valuevariables返回任何东西,但没有下面的调整。 此外,我需要返回完整的属性字典,以确定结果的行动。 我会把这个在评论中,但似乎没有代表这样做。 如果我违反了协议,提前道歉。

具体的调整是使用textView.textStorage而不是textView.attributedText 。 作为一名仍在学习iOS的程序员,我不太确定这是为什么,但也许别人可以启发我们。

水龙头处理方法的具体修改:

  NSDictionary *attributesOfTappedText = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range]; 

在我的视图控制器完整的代码

 - (void)viewDidLoad { [super viewDidLoad]; self.textView.attributedText = [self attributedTextViewString]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)]; [self.textView addGestureRecognizer:tap]; } - (NSAttributedString *)attributedTextViewString { NSMutableAttributedString *paragraph = [[NSMutableAttributedString alloc] initWithString:@"This is a string with " attributes:@{NSForegroundColorAttributeName:[UIColor blueColor]}]; NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a tappable string" attributes:@{@"tappable":@(YES), @"networkCallRequired": @(YES), @"loadCatPicture": @(NO)}]; NSAttributedString* anotherAttributedString = [[NSAttributedString alloc] initWithString:@" and another tappable string" attributes:@{@"tappable":@(YES), @"networkCallRequired": @(NO), @"loadCatPicture": @(YES)}]; [paragraph appendAttributedString:attributedString]; [paragraph appendAttributedString:anotherAttributedString]; return [paragraph copy]; } - (void)textTapped:(UITapGestureRecognizer *)recognizer { UITextView *textView = (UITextView *)recognizer.view; // Location of the tap in text-container coordinates NSLayoutManager *layoutManager = textView.layoutManager; CGPoint location = [recognizer locationInView:textView]; location.x -= textView.textContainerInset.left; location.y -= textView.textContainerInset.top; NSLog(@"location: %@", NSStringFromCGPoint(location)); // Find the character that's been tapped on NSUInteger characterIndex; characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:textView.textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; if (characterIndex < textView.textStorage.length) { NSRange range; NSDictionary *attributes = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range]; NSLog(@"%@, %@", attributes, NSStringFromRange(range)); //Based on the attributes, do something ///if ([attributes objectForKey:...)] //make a network call, load a cat Pic, etc } } 

使用iOS 7进行自定义链接和做自己想做的事情变得更加容易。Ray Wenderlich有很好的例子

WWDC 2013例子 :

 NSLayoutManager *layoutManager = textView.layoutManager; CGPoint location = [touch locationInView:textView]; NSUInteger characterIndex; characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:textView.textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; if (characterIndex < textView.textStorage.length) { // valid index // Find the word range here // using -enumerateSubstringsInRange:options:usingBlock: } 

我能够用NSLinkAttributeName简单地解决这个问题

Swift 2

 class MyClass: UIViewController, UITextViewDelegate { @IBOutlet weak var tvBottom: UITextView! override func viewDidLoad() { super.viewDidLoad() let attributedString = NSMutableAttributedString(string: "click me ok?") attributedString.addAttribute(NSLinkAttributeName, value: "cs://moreinfo", range: NSMakeRange(0, 5)) tvBottom.attributedText = attributedString tvBottom.delegate = self } func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool { UtilityFunctions.alert("clicked", message: "clicked") return false } } 

可以使用characterIndexForPoint:inTextContainer:fractionOfDistanceBetweenInsertionPoints: 它的工作方式与你想要的有所不同 – 你必须testing一个被点击的angular色是否属于一个魔法字 。 但它不应该是复杂的。

顺便说一下,我强烈build议您观看WWDC 2013上的“ 介绍文本工具包”

使用Swift 3检测属性文本的完整示例

 let termsAndConditionsURL = TERMS_CONDITIONS_URL; let privacyURL = PRIVACY_URL; override func viewDidLoad() { super.viewDidLoad() self.txtView.delegate = self let str = "By continuing, you accept the Terms of use and Privacy policy" let attributedString = NSMutableAttributedString(string: str) var foundRange = attributedString.mutableString.range(of: "Terms of use") //mention the parts of the attributed text you want to tap and get an custom action attributedString.addAttribute(NSLinkAttributeName, value: termsAndConditionsURL, range: foundRange) foundRange = attributedString.mutableString.range(of: "Privacy policy") attributedString.addAttribute(NSLinkAttributeName, value: privacyURL, range: foundRange) txtView.attributedText = attributedString } 

然后你可以使用shouldInteractWith URL UITextViewDelegate委托方法来捕捉动作。 shouldInteractWith URL确保你已经正确设置了委托。

 func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { let storyboard = UIStoryboard(name: "Main", bundle: nil) let vc = storyboard.instantiateViewController(withIdentifier: "WebView") as! SKWebViewController if (URL.absoluteString == termsAndConditionsURL) { vc.strWebURL = TERMS_CONDITIONS_URL self.navigationController?.pushViewController(vc, animated: true) } else if (URL.absoluteString == privacyURL) { vc.strWebURL = PRIVACY_URL self.navigationController?.pushViewController(vc, animated: true) } return false } 

就像明智一样,您可以根据您的要求执行任何操作。

干杯!!

这一个可能工作正常与短链接,在textview中的多链接。 它适用于iOS 6,7,8。

 - (void)tappedTextView:(UITapGestureRecognizer *)tapGesture { if (tapGesture.state != UIGestureRecognizerStateEnded) { return; } UITextView *textView = (UITextView *)tapGesture.view; CGPoint tapLocation = [tapGesture locationInView:textView]; NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber error:nil]; NSArray* resultString = [detector matchesInString:self.txtMessage.text options:NSMatchingReportProgress range:NSMakeRange(0, [self.txtMessage.text length])]; BOOL isContainLink = resultString.count > 0; if (isContainLink) { for (NSTextCheckingResult* result in resultString) { CGRect linkPosition = [self frameOfTextRange:result.range inTextView:self.txtMessage]; if(CGRectContainsPoint(linkPosition, tapLocation) == 1){ if (result.resultType == NSTextCheckingTypePhoneNumber) { NSString *phoneNumber = [@"telprompt://" stringByAppendingString:result.phoneNumber]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]]; } else if (result.resultType == NSTextCheckingTypeLink) { [[UIApplication sharedApplication] openURL:result.URL]; } } } } } - (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView { UITextPosition *beginning = textView.beginningOfDocument; UITextPosition *start = [textView positionFromPosition:beginning offset:range.location]; UITextPosition *end = [textView positionFromPosition:start offset:range.length]; UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end]; CGRect firstRect = [textView firstRectForRange:textRange]; CGRect newRect = [textView convertRect:firstRect fromView:textView.textInputView]; return newRect; }