带有手势识别器的 UITextView - 有条件地向前触摸到父视图
Posted
技术标签:
【中文标题】带有手势识别器的 UITextView - 有条件地向前触摸到父视图【英文标题】:UITextView with Gesture Recognizer - Conditionally Forward Touch to Parent View 【发布时间】:2018-09-28 10:19:26 【问题描述】:我在UITableViewCell
中嵌入了一个UITextView
。
文本视图已禁用滚动,并随着其中的文本增加高度。
文本视图有一个类似链接的文本部分,该部分具有不同的颜色并带有下划线,并且我在文本视图上附加了一个点击手势识别器,用于检测用户是否点击文本的“链接”部分是否(这是使用文本视图的layoutManager
和textContainerInset
来检测点击是否落在“链接”内。它基本上是一个自定义的命中测试功能) .
我希望表格视图单元格接收点击并在用户“错过”文本视图的链接部分时被选中,但不知道该怎么做。 p>
文本视图将userInteractionEnabled
设置为true
。但是,当没有附加手势识别器时,这不会阻止触摸到达表格视图单元格。
相反,如果我将其设置为false
,由于某种原因,单元格选择会完全停止,即使在文本视图边界之外点击(但手势识别器仍然有效.. . 为什么?)。
我的尝试
我尝试过覆盖gestureRecognizer(_ :shouldReceive:)
,但即使我返回false
,表格视图单元格也不会被选中...
我也尝试过实现gestureRecognizerShouldBegin(_:)
,但在那里,即使我执行命中测试并返回false
,单元格也不会被点击。
如何将错过的点击转发回单元格以突出显示它?
【问题讨论】:
是否可以为 UITextView 创建一个自定义类并覆盖pointInside:withEvent
,并且在此方法中您可以执行链接命中逻辑,如果它返回 false,则应该将触摸转发到细胞。
@danypata 是的,我想过,但希望有一个更优雅的解决方案,不需要额外的子类化......
我认为下面的答案比我的蛮力建议更优雅。
【参考方案1】:
保持所有视图处于活动状态(即启用用户交互)。
循环浏览文本视图的手势并禁用不需要的手势。
循环遍历表格视图的gestureRecognisers
数组,并使用requireGestureRecognizerToFail 使它们依赖于文本视图的自定义点击手势。
如果它是静态表视图,您可以在视图加载时执行此操作。对于动态表格视图,请在文本视图单元格的“willDisplayCell”中执行此操作。
【讨论】:
谢谢,我试试看。 嗯,它似乎根本不起作用。理想情况下,我希望将所有代码包含在表格视图 cell... 中【参考方案2】:在尝试Swapnil Luktuke's answer(至少在我理解的范围内)无济于事之后,以及所有可能的组合:
实现UIGestureRecognizerDelegate
的方法,
覆盖UITapGestureRecognizer
,
有条件地调用ignore(_:for:)
等
(也许在绝望中我错过了一些明显的东西,但谁知道......)
...我放弃了,决定按照 cmets 中 @danyapata 的建议来解决我的问题,并子类 UITextView。
部分基于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
像往常一样,我会等几天再接受我自己的答案,以防万一出现更好的情况......
【讨论】:
以上是关于带有手势识别器的 UITextView - 有条件地向前触摸到父视图的主要内容,如果未能解决你的问题,请参考以下文章
ios - 带有 UITextView 的 UITapGestureRecognizer