UITextView:禁用select,允许链接

我有一个显示NSAttributedStringUITextView 。 textView的editableselectable属性都设置为false

belongsString包含一个URL,我想允许点击URL来打开浏览器。 但是只有当selectable属性设置为true才能与URL进行交互。

我如何允许用户交互只用于点击链接,但不能用于select文本?

我发现摆弄内部手势识别器的概念有点吓人,所以试图find另一种解决scheme。 我发现我们可以覆盖point(inside:with:)有效地允许一个“点击通过”,当用户没有触及文本中的链接:

 // Inside a UITextView subclass: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { guard let pos = closestPosition(to: point) else { return false } guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: UITextLayoutDirection.left.rawValue) else { return false } let startIndex = offset(from: beginningOfDocument, to: range.start) return attributedText.attribute(NSLinkAttributeName, at: startIndex, effectiveRange: nil) != nil } 

这也意味着,如果你有一个UITableViewCell的链接UITextViewtableView(didSelectRowAt:)仍然被调用时,点击文本的非链接部分:)

所以经过一番研究,我已经find了解决办法。 这是一个黑客攻击,我不知道它是否会在未来的iOS版本,但它现在工作(iOS 9.3)。

只需添加这个UITextView类别(Gist 在这里 ):

 @implementation UITextView (NoFirstResponder) - (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer { if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { @try { id targetAndAction = ((NSMutableArray *)[gestureRecognizer valueForKey:@"_targets"]).firstObject; NSArray <NSString *>*actions = @[@"action=loupeGesture:", // link: no, selection: shows circle loupe and blue selectors for a second @"action=longDelayRecognizer:", // link: no, selection: no /*@"action=smallDelayRecognizer:", // link: yes (no long press), selection: no*/ @"action=oneFingerForcePan:", // link: no, selection: shows rectangular loupe for a second, no blue selectors @"action=_handleRevealGesture:"]; // link: no, selection: no for (NSString *action in actions) { if ([[targetAndAction description] containsString:action]) { [gestureRecognizer setEnabled:false]; } } } @catch (NSException *e) { } @finally { [super addGestureRecognizer: gestureRecognizer]; } } } 

Swift 3.0

对于上面的Objective-C版本通过@Lukas

 extension UITextView { override open func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) { if gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) { do { let array = try gestureRecognizer.value(forKey: "_targets") as! NSMutableArray let targetAndAction = array.firstObject let actions = ["action=oneFingerForcePan:", "action=_handleRevealGesture:", "action=loupeGesture:", "action=longDelayRecognizer:"] for action in actions { print("targetAndAction.debugDescription: \(targetAndAction.debugDescription)") if targetAndAction.debugDescription.contains(action) { gestureRecognizer.isEnabled = false } } } catch let exception { print("TXT_VIEW EXCEPTION : \(exception)") } defer { super.addGestureRecognizer(gestureRecognizer) } } } } 

iOS 11,beta 6更新 :这是迄今为止我发现的最简单,最具前景的解决scheme,除了使用TextKit滚动我们自己的UITextView TextKit

斯威夫特4

第1部分

 public class ReadOnlyUITextView: UITextView { fileprivate lazy var transparentCoveringView = UIView() override public func updateConstraints() { if transparentCoveringView.constraints.isEmpty { addSubview(transparentCoveringView) bringSubview(toFront: transparentCoveringView) transparentCoveringView.backgroundColor = UIColor.clear // Helper method. transparentCoveringView.addFillViewConstraints(in: self) } super.updateConstraints() } /** Override `becomeFirstResponder()`and return false to disable double-tap selection of links. */ override public func becomeFirstResponder() -> Bool { return false } } 

第2部分

如果你需要添加一个UILongPressGestureRecognizerReadOnlyUITextView实例,就像我的情况那样, 将它的minimumPressDuration属性设置为小于0.325 ,这样它会在所有系统定义的长按识别器之前触发。

就这样 :)