UITextView:禁用select,允许链接
我有一个显示NSAttributedString
的UITextView
。 textView的editable
和selectable
属性都设置为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
的链接UITextView
, tableView(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部分 :
如果你需要添加一个UILongPressGestureRecognizer
到ReadOnlyUITextView
实例,就像我的情况那样, 将它的minimumPressDuration
属性设置为小于0.325 ,这样它会在所有系统定义的长按识别器之前触发。
就这样 :)