试过之后
Swapnil Luktuke's answer
(至少在我理解的范围内)没有任何效果,所有可能的组合:
-
实施
UIGestureRecognizerDelegate
,
-
覆盖
UITapGestureRecognizer
-
有条件呼叫
ignore(_:for:)
等等。
(也许在我绝望的时候,我错过了一些显而易见的东西,但谁知道呢……)
部分基于在上找到的代码
this Medium post
,我想到了这个
UITextView
import UIKit
/**
Detects taps on subregions of its attributed text that correspond to custom,
named attributes.
- note: If no tap is detected, the behavior is equivalent to a text view with
`isUserInteractionEnabled` set to `false` (i.e., touches "pass through"). The
same behavior doesn't seem to be easily implemented using just stock
`UITextView` and gesture recognizers (hence the need to subclass).
*/
class LinkTextView: UITextView {
private var tapHandlersByName: [String: [(() -> Void)]] = [:]
/**
Adds a custom block to be executed wjhen a tap is detected on a subregion
of the **attributed** text that contains the attribute named accordingly.
*/
public func addTapHandler(_ handler: @escaping(() -> Void), forAttribute attributeName: String) {
var handlers = tapHandlersByName[attributeName] ?? []
handlers.append(handler)
tapHandlersByName[attributeName] = handlers
}
// MARK: - Initialization
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
commonSetup()
}
private func commonSetup() {
self.delaysContentTouches = false
self.isScrollEnabled = false
self.isEditable = false
self.isUserInteractionEnabled = true
}
// MARK: - UIView
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let attributeName = self.attributeName(at: point), let handlers = tapHandlersByName[attributeName], handlers.count > 0 else {
return nil // Ignore touch
}
return self // Claim touch
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
// find attribute name
guard let touch = touches.first, let attributeName = self.attributeName(at: touch.location(in: self)) else {
return
}
// Execute all handlers for that attribute, once:
tapHandlersByName[attributeName]?.forEach({ (handler) in
handler()
})
}
// MARK: - Internal Support
private func attributeName(at point: CGPoint) -> String? {
let location = CGPoint(
x: point.x - self.textContainerInset.left,
y: point.y - self.textContainerInset.top)
let characterIndex = self.layoutManager.characterIndex(
for: location,
in: self.textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
guard characterIndex < self.textStorage.length else {
return nil
}
let firstAttributeName = tapHandlersByName.allKeys.first { (attributeName) -> Bool in
if self.textStorage.attribute(NSAttributedStringKey(rawValue: attributeName), at: characterIndex, effectiveRange: nil) != nil {
return true
}
return false
}
return firstAttributeName
}
}
通常,我会等几天再接受自己的答案,以防万一会有更好的结果出现。。。