带有手势识别器的 UITextView - 有条件地将触摸转发到父视图

尼古拉斯·米亚里

我有一个UITextView嵌入在UITableViewCell.

文本视图已禁用滚动,并且随着其中的文本增加高度。

文本视图有一个类似链接的文本部分,它具有不同的颜色和下划线,我有一个附加到文本视图点击手势识别器,用于检测用户是否点击了文本的“链接”部分(这是使用文本视图完成的,layoutManagertextContainerInset检测点击是否落在“链接”内。它基本上是一个自定义的命中测试功能)。


我希望表格视图单元格接收点击并在用户“错过”文本视图的链接部分时被选中,但无法弄清楚如何去做。


文本视图已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] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

删除所有UITextView的手势识别器

来自分类Dev

带有UITableViewCell内的手势识别器的UILabel阻止didSelectRowAtIndexPath

来自分类Dev

如何使UIScrollView滚动并具有手势识别器?

来自分类Dev

带有手势的UIButton控件

来自分类Dev

如何使用Xcode中的LLDB将所有手势识别器附加到视图?

来自分类Dev

将触摸和手势从UIScrollview转发到视图

来自分类Dev

带有标签 + 按钮的 UIView - 无法识别点击手势

来自分类Dev

手势识别器阻止触摸已结束

来自分类Dev

如何在Swift中从UIView删除所有手势识别器

来自分类Dev

Flutter 是否有“滚动手势识别器”小部件?

来自分类Dev

iOS手势识别器未处理子视图

来自分类Dev

子视图手势识别器未调用

来自分类Dev

iOS手势识别器未处理子视图

来自分类Dev

我的手势识别器附加到错误的视图

来自分类Dev

子视图手势识别器未调用

来自分类Dev

多个视图上的手势识别器回调

来自分类Dev

将多个参数传递给手势识别器

来自分类Dev

将平移手势识别器限制为imageview

来自分类Dev

将现有的活动手势识别器传递给模态视图控制器

来自分类Dev

如何在Xcode中使用LLDB将所有手势识别器附加到视图?

来自分类Dev

带有DatePicker的UITextView

来自分类Dev

带有滑动手势的UIButton控件

来自分类Dev

带有手势的Android大图

来自分类Dev

为什么 iOS 中点击手势识别器的界面构建器操作没有触发?

来自分类Dev

将手势识别器放在视图或控制器类中

来自分类Dev

我可以将手势识别器的代码放在视图的子类中吗?

来自分类Dev

UITableView无法通过单个手势识别器进行触摸

来自分类Dev

在Swift的手势识别器中获取触摸的窗口坐标?

来自分类Dev

子视图外部视图范围内的手势识别器

Related 相关文章

  1. 1

    删除所有UITextView的手势识别器

  2. 2

    带有UITableViewCell内的手势识别器的UILabel阻止didSelectRowAtIndexPath

  3. 3

    如何使UIScrollView滚动并具有手势识别器?

  4. 4

    带有手势的UIButton控件

  5. 5

    如何使用Xcode中的LLDB将所有手势识别器附加到视图?

  6. 6

    将触摸和手势从UIScrollview转发到视图

  7. 7

    带有标签 + 按钮的 UIView - 无法识别点击手势

  8. 8

    手势识别器阻止触摸已结束

  9. 9

    如何在Swift中从UIView删除所有手势识别器

  10. 10

    Flutter 是否有“滚动手势识别器”小部件?

  11. 11

    iOS手势识别器未处理子视图

  12. 12

    子视图手势识别器未调用

  13. 13

    iOS手势识别器未处理子视图

  14. 14

    我的手势识别器附加到错误的视图

  15. 15

    子视图手势识别器未调用

  16. 16

    多个视图上的手势识别器回调

  17. 17

    将多个参数传递给手势识别器

  18. 18

    将平移手势识别器限制为imageview

  19. 19

    将现有的活动手势识别器传递给模态视图控制器

  20. 20

    如何在Xcode中使用LLDB将所有手势识别器附加到视图?

  21. 21

    带有DatePicker的UITextView

  22. 22

    带有滑动手势的UIButton控件

  23. 23

    带有手势的Android大图

  24. 24

    为什么 iOS 中点击手势识别器的界面构建器操作没有触发?

  25. 25

    将手势识别器放在视图或控制器类中

  26. 26

    我可以将手势识别器的代码放在视图的子类中吗?

  27. 27

    UITableView无法通过单个手势识别器进行触摸

  28. 28

    在Swift的手势识别器中获取触摸的窗口坐标?

  29. 29

    子视图外部视图范围内的手势识别器

热门标签

归档