我有一个UITextView
嵌入在UITableViewCell
.
文本视图已禁用滚动,并且随着其中的文本增加高度。
文本视图有一个类似链接的文本部分,它具有不同的颜色和下划线,我有一个附加到文本视图的点击手势识别器,用于检测用户是否点击了文本的“链接”部分(这是使用文本视图完成的,layoutManager
并textContainerInset
检测点击是否落在“链接”内。它基本上是一个自定义的命中测试功能)。
我希望表格视图单元格接收点击并在用户“错过”文本视图的链接部分时被选中,但无法弄清楚如何去做。
文本视图已userInteractionEnabled
设置为true
。然而,当没有附加手势识别器时,这不会阻止触摸到达表格视图单元格。
相反,如果我将它设置为false
,出于某种原因,单元格选择完全停止,即使在文本视图的边界之外轻敲(但手势识别器仍然有效...... 为什么?)。
我试过覆盖gestureRecognizer(_ :shouldReceive:)
,但即使我返回false
,表视图单元格也没有被选中......
我也试过实现gestureRecognizerShouldBegin(_:)
,但在那里,即使我执行了命中测试并返回false
,单元也没有得到点击。
如何将错过的点击转发回单元格,以突出显示它?
在尝试了Swapnil Luktuke 的答案(至少在我理解的范围内)无济于事之后,以及以下所有可能的组合:
UIGestureRecognizerDelegate
,UITapGestureRecognizer
,ignore(_:for:)
等(也许在绝望中我错过了一些明显的东西,但谁知道......)
...我放弃并决定遵循@danyapata 在对我的问题的评论中的建议,并将UITextView 子类化。
部分基于在这个 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
}
}
像往常一样,我会等几天再接受我自己的答案,以防万一出现更好的东西......
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句