代码之家  ›  专栏  ›  技术社区  ›  Nicolas Miari

带手势识别器的UITextView-有条件地向前触摸到父视图

  •  0
  • Nicolas Miari  · 技术社区  · 7 年前

    UITextView 嵌入 UITableViewCell .

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

    文本视图有一个类似链接的文本部分,该部分具有不同的颜色和下划线,我有一个 轻触手势识别器 (这是使用文本视图的 layoutManager textContainerInset 以检测抽头是否在“链接”内。它基本上是一个定制的命中测试函数)。


    但不知道怎么做。


    文本视图具有 userInteractionEnabled true . 但是,当没有连接手势识别器时,这不会阻止触摸到达表视图单元格。

    相反,如果我把它设为 false ,由于某些原因,单元格选择完全停止,即使在点击时也是如此 仍然有效。。。 为什么? ).


    我试过的

    我已经试过了 gestureRecognizer(_ :shouldReceive:) 但即使我回来 ,表视图单元格未被选中。。。

    gestureRecognizerShouldBegin(_:) ,但也有,即使我执行命中测试并返回


    我怎样才能把漏掉的点击转发回牢房,突出显示出来?

    2 回复  |  直到 7 年前
        1
  •  1
  •   Swapnil Luktuke    7 年前

    保持所有视图处于活动状态(即启用用户交互)。

    循环表视图的 gestureRecognisers 数组,并使它们依赖于文本视图的自定义点击手势 requireGestureRecognizerToFail .

    如果是静态表视图,则可以在视图中执行此操作。对于动态表视图,请在文本视图单元格的“willDisplayCell”中执行此操作。

        2
  •  1
  •   Nicolas Miari    7 年前

    试过之后 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
        }
    }
    

    通常,我会等几天再接受自己的答案,以防万一会有更好的结果出现。。。