UITextView link 可选,其余文本不可选

UITextView link selectable without rest of text being selectable

我正在尝试获得类似于 Facebook 使用的设置(如果他们使用 UITextView)。我希望 links 被自动检测到,但是我不希望 UITextView selectable 中的任何其他文本。因此,用户可以单击 link 但无法 select 任何其他文本。

尽管四处搜索,但我还没有找到 link selection 要工作的解决方案,它需要整个文本视图 select 可用。

此答案适用于 iOS10.3.x 及以下您的 UIView 未嵌入子视图的地方。如需更强大、更现代的答案,请参阅 .

您需要防止 UITextView 成为第一响应者。

1。 Subclass UITextView 到您自己的自定义 class (MyTextView).

2。覆盖 canBecomeFirstResponder()。这是 Swift 中的示例:

Swift 3:

class MyTextView: UITextView {
    override func becomeFirstResponder() -> Bool {
        return false
    }
}

Swift 2:

class MyTextView: UITextView {
    override func canBecomeFirstResponder() -> Bool {
        return false
    }
}

检测到的任何链接仍将启用。我用 phone 数字测试了这个。

您需要继承 UITextView 并覆盖 gestureRecognizerShouldBegin (_:) 方法,如下所示:

override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    if isEditable == false {
        if let gesture =  gestureRecognizer as? UILongPressGestureRecognizer, gesture.minimumPressDuration == 0.5 {
            return false
        }
    }
    return true
}

这将防止 textview 被 selected 但 link 将按预期工作

已编辑: 事实证明,当双击并按住时,您仍然可以 select 文本。正如我发现它发生在两次点击之后(不是带有 属性 "minimalNumberOfTaps" 的 UITapGesture,而是一个接一个地不同的点击),所以解决方案是在第一步之后跟踪时间(大约 0.7 秒) 完整代码:

var lastTapTime: TimeInterval = 0
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if isEditable == false {
            if let gesture =  gestureRecognizer as? UILongPressGestureRecognizer, gesture.minimumPressDuration == 0.5 {
        return false
            }
        }
        if Date().timeIntervalSince1970 >= lastTapTime + 0.7 {
            lastTapTime = Date().timeIntervalSince1970
            return true
        } else {
            return false
        }
    }

这不是最优雅的解决方案,但它似乎有效 ‍♂️

我选择的答案不适用,我不喜欢比较内部 UIGestureRecognizers 中未确认的值。

我的解决方案是覆盖 point(inside:with:) 并允许在用户未点击链接文本时点击:

这对我有用;

class LinkDetectingTextView: UITextView {
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if isEditable == false {
            if let _ = gestureRecognizer as? UITapGestureRecognizer {
                return false
            }

            if let longPressRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
                longPressRecognizer.minimumPressDuration == 0.5 { // prevent to select text but allow certain functionality in application

                return false
            }
        }

        return true
    }
}

此外,在应用程序中将 longPressGestureRecognizer 的 minimumPressDuration 设置为不同于 0.5 的另一个值。

如果您的最低部署目标是 iOS 11.2 或更高版本

您可以通过子类化 UITextView 并禁止可以 select 某些东西的手势来禁用文本 selection。

下面的解决方案是:

  • 与 isEditable 兼容
  • 与 isScrollEnabled 兼容
  • 与链接兼容
/// Class to allow links but no selection.
/// Basically, it disables unwanted UIGestureRecognizer from UITextView.
/// 
class UnselectableTappableTextView: UITextView {

    // required to prevent blue background selection from any situation
    override var selectedTextRange: UITextRange? {
        get { return nil }
        set {}
    }

    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer is UIPanGestureRecognizer {
            // required for compatibility with isScrollEnabled
            return super.gestureRecognizerShouldBegin(gestureRecognizer)
        }
        if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
            tapGestureRecognizer.numberOfTapsRequired == 1 {
            // required for compatibility with links
            return super.gestureRecognizerShouldBegin(gestureRecognizer)
        }
        // allowing smallDelayRecognizer for links
        // 
        if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
            // comparison value is used to distinguish between 0.12 (smallDelayRecognizer) and 0.5 (textSelectionForce and textLoupe)
            longPressGestureRecognizer.minimumPressDuration < 0.325 {
            return super.gestureRecognizerShouldBegin(gestureRecognizer)
        }
        // preventing selection from loupe/magnifier (_UITextSelectionForceGesture), multi tap, tap and a half, etc.
        gestureRecognizer.isEnabled = false
        return false
    }
}

如果您的最低部署目标是 iOS 11.1 或更早版本

原生 UITextView 链接手势识别器在 iOS 11.0-11.1 上损坏,需要 小延迟长按 而不是 点击: Xcode 9 UITextView links no longer clickable

您可以使用自己的手势识别器正确支持链接,并且可以通过子类化 UITextView 禁用文本 selection 并禁止可以 select 某些东西或点击某些东西的手势。

下面的解决方案将不允许 selection 并且是:

  • 与 isScrollEnabled 兼容
  • 与链接兼容
  • iOS 11.0 和 iOS 11.1 的解决方法限制,但在点击文本附件时失去 UI 效果
/// Class to support links and to disallow selection.
/// It disables most UIGestureRecognizer from UITextView and adds a UITapGestureRecognizer.
/// 
class UnselectableTappableTextView: UITextView {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // Native UITextView links gesture recognizers are broken on iOS 11.0-11.1:
        // 
        // So we add our own UITapGestureRecognizer.
        linkGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped))
        linkGestureRecognizer.numberOfTapsRequired = 1
        addGestureRecognizer(linkGestureRecognizer)
        linkGestureRecognizer.isEnabled = true
    }

    var linkGestureRecognizer: UITapGestureRecognizer!

    // required to prevent blue background selection from any situation
    override var selectedTextRange: UITextRange? {
        get { return nil }
        set {}
    }

    override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
        // Prevents drag and drop gestures,
        // but also prevents a crash with links on iOS 11.0 and 11.1.
        // 
        gestureRecognizer.isEnabled = false
        super.addGestureRecognizer(gestureRecognizer)
    }

    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer == linkGestureRecognizer {
            // Supporting links correctly.
            return super.gestureRecognizerShouldBegin(gestureRecognizer)
        }
        if gestureRecognizer is UIPanGestureRecognizer {
            // Compatibility support with isScrollEnabled.
            return super.gestureRecognizerShouldBegin(gestureRecognizer)
        }
        // Preventing selection gestures and disabling broken links support.
        gestureRecognizer.isEnabled = false
        return false
    }

    @objc func textTapped(recognizer: UITapGestureRecognizer) {
        guard recognizer == linkGestureRecognizer else {
            return
        }
        var location = recognizer.location(in: self)
        location.x -= textContainerInset.left
        location.y -= textContainerInset.top
        let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        let characterRange = NSRange(location: characterIndex, length: 1)

        if let attachment = attributedText?.attribute(.attachment, at: characterIndex, effectiveRange: nil) as? NSTextAttachment {
            if #available(iOS 10.0, *) {
                _ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange, interaction: .invokeDefaultAction)
            } else {
                _ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange)
            }
        }
        if let url = attributedText?.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL {
            if #available(iOS 10.0, *) {
                _ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange, interaction: .invokeDefaultAction)
            } else {
                _ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange)
            }
        }
    }
}

您可以子类化 UITextView 覆盖 selectedTextRange 的方法,将其设置为 nil。 link 仍然可以点击,但您将无法 select 其余文本(即使是 link,但您可以点击它)。

class CustomTextView: UITextView {
override public var selectedTextRange: UITextRange? {
    get {
        return nil
    }
    set { }
}