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

tar*_*mes 114 objective-c uitextview ios textkit

我有一个显示NSAttributedString的UITextView.这个字符串包含我想要点击的单词,这样当它们被点击时我会被回叫以便我可以执行一个动作.我意识到UITextView可以检测URL上的点击并回调我的委托,但这些不是URL.

在我看来,iOS7和TextKit的强大功能现在应该可行,但我找不到任何示例,我不知道从哪里开始.

我知道现在可以在字符串中创建自定义属性(虽然我还没有这样做),也许这些对于检测是否有一个神奇的单词被点击是有用的?在任何情况下,我仍然不知道如何拦截该敲击并检测敲击发生在哪个单词上.

请注意,不需要iOS 6兼容性.

tar*_*mes 114

我只想帮助别人一点.继施密特的回应之后,我可以完全按照我在原始问题中提出的要求行事.

1)创建一个属性字符串,其中自定义属性应用于可单击的单词.例如.

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

2)创建一个UITextView以显示该字符串,并向其添加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);

    }
}
Run Code Online (Sandbox Code Playgroud)

当你知道怎么做时那么容易!


Sur*_*gch 54

使用Swift检测属性文本的点击

有时对于初学者来说,知道如何设置(这对我而言)有点困难,所以这个例子有点全面.

添加UITextView到您的项目.

出口

连接UITextViewViewController名为的插座textView.

自定义属性

我们将通过制作扩展来创建自定义属性.

注意:此步骤在技术上是可选的,但如果您不这样做,则需要编辑下一部分中的代码以使用标准属性NSAttributedString.Key.foregroundColor.使用自定义属性的优点是您可以定义要在属性文本范围中存储的值.

使用File> New> File ...> iOS> Source> Swift File添加一个新的swift文件.你可以称之为你想要的.我正在调用我的NSAttributedStringKey + CustomAttribute.swift.

粘贴以下代码:

import Foundation

extension NSAttributedString.Key {
    static let myAttributeName = NSAttributedString.Key(rawValue: "MyCustomAttribute")
}
Run Code Online (Sandbox Code Playgroud)

用以下内容替换ViewController.swift中的代码.请注意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 = [ NSAttributedString.Key.myAttributeName: "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)
    }

    @objc 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 = NSAttributedString.Key.myAttributeName
            let attributeValue = myTextView.attributedText?.attribute(attributeName, at: characterIndex, effectiveRange: nil)
            if let value = attributeValue {
                print("You tapped on \(attributeName.rawValue) and the value is: \(value)")
            }

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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

character index: 1
character at index: w
You tapped on MyCustomAttribute and the value is: some value
Run Code Online (Sandbox Code Playgroud)

笔记

  • 在这里,我使用了一个自定义属性,但它可以很容易地NSAttributedString.Key.foregroundColor(文本颜色)具有值UIColor.green.
  • 以前文本视图无法编辑或选择,但在我更新的Swift 4.2答案中,无论是否选中,它似乎都能正常工作.

进一步研究

这个答案是基于这个问题的其他几个答案.除此之外,另见


nat*_*203 32

这是一个稍微修改过的版本,基于@tarmes答案.我无法让value变量返回任何内容,但null没有下面的调整.此外,我需要返回完整的属性字典,以确定结果操作.我会把它放在评论中,但似乎没有代表这样做.如果我违反了协议,请提前道歉.

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

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

    NSDictionary *attributesOfTappedText = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];
Run Code Online (Sandbox Code Playgroud)

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

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

    }
}
Run Code Online (Sandbox Code Playgroud)


Adi*_*hur 24

使用iOS 7制作自定义链接并在点击上执行您想要的操作变得更加容易.Ray Wenderlich有很好的例子

  • 问题是 textView 需要是可选择的,我不想要这种行为。 (2认同)

Shm*_*idt 13

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:
}
Run Code Online (Sandbox Code Playgroud)


Jas*_*son 10

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

斯威夫特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
  }

}
Run Code Online (Sandbox Code Playgroud)


Aki*_*ala 7

使用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
}
Run Code Online (Sandbox Code Playgroud)

然后可以使用shouldInteractWith URLUITextViewDelegate委托方法来捕获操作。因此,请确保已正确设置了委托。

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
    }
Run Code Online (Sandbox Code Playgroud)

同样,您可以根据需要执行任何操作。

干杯!!