检测 iOS 中 UITextView 中属性文本的点击
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19332283/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Detecting taps on attributed text in a UITextView in iOS
提问by tarmes
I have a UITextView
which displays an NSAttributedString
. This string contains words that I'd like to make tappable, such that when they are tapped I get called back so that I can perform an action. I realise that UITextView
can detect taps on a URL and call back my delegate, but these aren't URLs.
我有一个UITextView
显示NSAttributedString
. 这个字符串包含我想要点击的单词,这样当它们被点击时我会被回调,以便我可以执行一个操作。我意识到UITextView
可以检测 URL 上的点击并回调我的委托,但这些不是 URL。
It seems to me that with iOS 7 and the power of TextKit this should now be possible, however I can't find any examples and I'm not sure where to start.
在我看来,有了 iOS 7 和 TextKit 的强大功能,这现在应该是可能的,但是我找不到任何示例,也不知道从哪里开始。
I understand that it's now possible to create custom attributes in the string (although I haven't done this yet), and perhaps these will be useful to detecting if one of the magic words has been tapped? In any case, I still don't know how to intercept that tap and detect on which word the tap occurred.
我知道现在可以在字符串中创建自定义属性(虽然我还没有这样做),也许这些对于检测是否已经点击了一个魔术词很有用?无论如何,我仍然不知道如何拦截那个点击并检测点击发生在哪个单词上。
Note that iOS 6 compatibility is notrequired.
需要注意的是iOS 6的兼容性不是必需的。
采纳答案by tarmes
I just wanted to help others a little more. Following on from Shmidt's response it's possible to do exactly as I had asked in my original question.
我只是想多帮助别人一点。继 Shmidt 的回答之后,可以完全按照我在原始问题中的要求进行操作。
1) Create an attributed string with custom attributes applied to the clickable words. eg.
1) 创建一个属性字符串,其中自定义属性应用于可点击的单词。例如。
NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a clickable word" attributes:@{ @"myCustomTag" : @(YES) }];
[paragraph appendAttributedString:attributedString];
2) Create a UITextView to display that string, and add a UITapGestureRecognizer to it. Then handle the tap:
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);
}
}
So easy when you know how!
当你知道怎么做的时候就这么简单!
回答by Suragch
Detecting taps on attributed text with Swift
使用 Swift 检测对属性文本的点击
Sometimes for beginners it is a little hard to know how to do get things set up (it was for me anyway), so this example is a little fuller.
有时对于初学者来说,知道如何进行设置有点困难(无论如何对我来说都是如此),所以这个例子更完整一些。
Add a UITextView
to your project.
将 a 添加UITextView
到您的项目中。
Outlet
出口
Connect the UITextView
to the ViewController
with an outlet named textView
.
使用名为 的插座将 连接UITextView
到。ViewController
textView
Custom attribute
自定义属性
We are going to make a custom attribute by making an Extension.
我们将通过制作Extension来制作自定义属性。
Note:This step is technically optional, but if you don't do it you will need to edit the code in the next part to use a standard attribute like
NSAttributedString.Key.foregroundColor
. The advantage of using a custom attribute is that you can define what values you want to store in the attributed text range.
注意:这一步在技术上是可选的,但如果你不这样做,你将需要编辑下一部分中的代码以使用标准属性,如
NSAttributedString.Key.foregroundColor
. 使用自定义属性的优点是您可以定义要在属性文本范围中存储的值。
Add a new swift file with File > New > File... > iOS > Source > Swift File. You can call it what you want. I am calling mine NSAttributedStringKey+CustomAttribute.swift.
使用File > New > File... > iOS > Source > Swift File添加一个新的 swift 文件。你可以随心所欲地称呼它。我打电话给我的NSAttributedStringKey+CustomAttribute.swift。
Paste in the following code:
粘贴以下代码:
import Foundation
extension NSAttributedString.Key {
static let myAttributeName = NSAttributedString.Key(rawValue: "MyCustomAttribute")
}
Code
代码
Replace the code in ViewController.swift with the following. Note the UIGestureRecognizerDelegate
.
将 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)")
}
}
}
}
Now if you tap on the "w" of "Swift", you should get the following result:
现在,如果您点击“Swift”的“w”,您应该得到以下结果:
character index: 1
character at index: w
You tapped on MyCustomAttribute and the value is: some value
Notes
笔记
- Here I used a custom attribute, but it could have just as easily been
NSAttributedString.Key.foregroundColor
(text color) that has a value ofUIColor.green
. - Formerly the text view could not be editable or selectable, but in my updated answer for Swift 4.2 it seems to be working fine no matter whether these are selected or not.
- 在这里,我使用了一个自定义属性,但它也可以很容易地
NSAttributedString.Key.foregroundColor
(文本颜色)具有UIColor.green
. - 以前,文本视图无法编辑或选择,但在我更新的 Swift 4.2 答案中,无论是否选择这些视图,它似乎都能正常工作。
Further study
进一步研究
This answer was based on several other answers to this question. Besides these, see also
这个答案是基于这个问题的其他几个答案。除了这些,另见
回答by natenash203
This is a slightly modified version, building off of @tarmes answer. I couldn't get the value
variable to return anything but null
without the tweak below. Also, I needed the full attribute dictionary returned in order to determine the resulting action. I would have put this in the comments but don't appear to have the rep to do so. Apologies in advance if I have violated protocol.
这是一个稍微修改过的版本,基于@tarmes 的答案。我无法让value
变量返回任何东西,但null
没有下面的调整。此外,我需要返回完整的属性字典以确定结果操作。我会把这个放在评论中,但似乎没有代表这样做。如果我违反了协议,请提前道歉。
Specific tweak is to use textView.textStorage
instead of textView.attributedText
. As a still learning iOS programmer, I am not really sure why this is, but perhaps someone else can enlighten us.
具体的调整是使用textView.textStorage
而不是textView.attributedText
. 作为一个仍在学习的 iOS 程序员,我不太确定这是为什么,但也许其他人可以启发我们。
Specific modification in the tap handling method:
在tap处理方法中的具体修改:
NSDictionary *attributesOfTappedText = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];
Full code in my view controller
我的视图控制器中的完整代码
- (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
}
}
回答by Aditya Mathur
Making custom link and doing what you want on the tap has become much easier with iOS 7. There is very good example at Ray Wenderlich
在 iOS 7 中制作自定义链接和做你想做的事情变得更加容易。Ray Wenderlich有一个很好的例子
回答by Shmidt
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:
}
回答by Jase Whatson
I was able to solve this pretty simply with NSLinkAttributeName
我能够用 NSLinkAttributeName 很简单地解决这个问题
Swift 2
斯威夫特 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
}
}
回答by Akila Wasala
Complete example for detect actions on attributed text with Swift 3
使用 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
}
And then you can catch the action with shouldInteractWith URL
UITextViewDelegate delegate method.So make sure you have set the delegate properly.
然后您可以使用shouldInteractWith URL
UITextViewDelegate 委托方法捕获操作。因此请确保您已正确设置委托。
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
}
Like wise you can perform any action according to your requirement.
同样,您可以根据自己的要求执行任何操作。
Cheers!!
干杯!!
回答by Arek Holko
It's possible to do that with characterIndexForPoint:inTextContainer:fractionOfDistanceBetweenInsertionPoints:
. It'll work somewhat differently than you wanted - you'll have to test if a tapped character belongs to a magic word. But it shouldn't be complicated.
可以用characterIndexForPoint:inTextContainer:fractionOfDistanceBetweenInsertionPoints:
. 它的工作方式与您想要的略有不同 - 您必须测试轻按的字符是否属于魔法词。但应该不会很复杂。
BTW I highly recommend watching Introducing Text Kitfrom WWDC 2013.
顺便说一句,我强烈建议您观看WWDC 2013 的Introducing Text Kit。
回答by Imanou Petit
With Swift 5 and iOS 12, you can create a subclass of UITextView
and override point(inside:with:)
with some TextKit implementation in order to make only some NSAttributedStrings
in it tappable.
使用 Swift 5 和 iOS 12,您可以创建一个子类UITextView
并point(inside:with:)
使用一些 TextKit 实现覆盖,以便仅使其中的一些NSAttributedStrings
可点击。
The following code shows how to create a UITextView
that only reacts to taps on underlined NSAttributedString
s in it:
以下代码显示了如何创建一个UITextView
只对点击其中带下划线的NSAttributedString
s做出反应的s:
InteractiveUnderlinedTextView.swift
InteractiveUnderlinedTextView.swift
import UIKit
class InteractiveUnderlinedTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
isScrollEnabled = false
isEditable = false
isSelectable = false
isUserInteractionEnabled = true
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let superBool = super.point(inside: point, with: event)
let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard characterIndex < textStorage.length else { return false }
let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil)
return superBool && attributes[NSAttributedString.Key.underlineStyle] != nil
}
}
ViewController.swift
视图控制器.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let linkTextView = InteractiveUnderlinedTextView()
linkTextView.backgroundColor = .orange
let mutableAttributedString = NSMutableAttributedString(string: "Some text\n\n")
let attributes = [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue]
let underlinedAttributedString = NSAttributedString(string: "Some other text", attributes: attributes)
mutableAttributedString.append(underlinedAttributedString)
linkTextView.attributedText = mutableAttributedString
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(underlinedTextTapped))
linkTextView.addGestureRecognizer(tapGesture)
view.addSubview(linkTextView)
linkTextView.translatesAutoresizingMaskIntoConstraints = false
linkTextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
linkTextView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
linkTextView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor).isActive = true
}
@objc func underlinedTextTapped(_ sender: UITapGestureRecognizer) {
print("Hello")
}
}
回答by Mol0ko
Use this extension for Swift:
将此扩展用于 Swift:
import UIKit
extension UITapGestureRecognizer {
func didTapAttributedTextInTextView(textView: UITextView, inRange targetRange: NSRange) -> Bool {
let layoutManager = textView.layoutManager
let locationOfTouch = self.location(in: textView)
let index = layoutManager.characterIndex(for: locationOfTouch, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(index, targetRange)
}
}
Add UITapGestureRecognizer
to your text view with following selector:
UITapGestureRecognizer
使用以下选择器添加到文本视图:
guard let text = textView.attributedText?.string else {
return
}
let textToTap = "Tap me"
if let range = text.range(of: tapableText),
tapGesture.didTapAttributedTextInTextView(textView: textTextView, inRange: NSRange(range, in: text)) {
// Tap recognized
}