带有可点击链接但没有文本突出显示的 UITextView

Posted

技术标签:

【中文标题】带有可点击链接但没有文本突出显示的 UITextView【英文标题】:UITextView with clickable links but no text highlighting 【发布时间】:2014-07-04 14:30:44 【问题描述】:

我有一个 UITextView 显示不可编辑的文本。我希望文本自动为用户解析链接、电话号码等,并且让那些可以点击。

不过,我不希望用户能够突出显示文本,因为我想覆盖那些长按和双击交互来做一些不同的事情。

为了在 ios7 中解析链接,需要为 UITextView 打开 Selectable 开关,但 Selectable 也启用了高亮,这是我不想要的。

我尝试覆盖 LongPress 手势以防止突出显示,但这似乎也禁用了对链接的普通点击...

for (UIGestureRecognizer *recognizer in cell.messageTextView.gestureRecognizers) 
    if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]])
        recognizer.enabled = NO;
    
    if ([recognizer isKindOfClass:[UITapGestureRecognizer class]])
        recognizer.enabled = YES;
    

那里有很多类似的主题,但似乎没有一个可以解决链接启用、文本无法突出显示的特定问题。

【问题讨论】:

【参考方案1】:

我正在解决完全相同的问题,我能做的最好的事情是通过将以下内容添加到 UITextView 的委托来立即清除选择:

- (void)textViewDidChangeSelection:(UITextView *)textView 
    if(!NSEqualRanges(textView.selectedRange, NSMakeRange(0, 0))) 
        textView.selectedRange = NSMakeRange(0, 0);
    

注意检查以防止递归。这几乎解决了这个问题,因为只有选择被禁用——链接仍然有效。

另一个无关紧要的问题是文本视图仍将成为第一响应者,您可以通过在设置所选范围后设置所需的第一响应者来解决此问题。

注意:唯一剩下的视觉怪异是按住会打开放大镜。

【讨论】:

有没有没有拿放大镜的? @Neelesh 将 textView.selectedTextRange 设置为 nil,但会存在递归。它很少见,我无法理解,但测试团队做到了。但我不确定您是否需要检查 selectedTextRange 是否为零,或者您可以在 selectedRange 上检查 Range(0,0)。【参考方案2】:

我不确定这是否适用于您的特定情况,但我有一个类似的情况,我需要 textview 链接是可点击的,但不希望发生文本选择,我正在使用 textview 在集合视图单元。

我只需要覆盖 -canBecomeFirstResponder 并返回 NO

@interface MYTextView : UITextView
@end

@implementation MYTextView

- (BOOL)canBecomeFirstResponder 
    return NO;


@end

【讨论】:

这似乎运作良好。比之后尝试清除选择要好得多。 也没有放大镜故障!这是一个更好的答案。 适用于 iOS 10.3 不适用于我,iOS 10.3.1 模拟器(注意,我的文本视图不可编辑)。该方法被调用,但文本选择仍然发生。【参考方案3】:

正如我在另一篇文章中所写,经过几次测试,我找到了另一个解决方案。

如果您希望链接处于活动状态并且不想启用选择,则需要编辑gestureRecognizers。

例如 - 有 3 个 LongPressGestureRecognizers。一个用于单击链接(minimumPressDuration = 0.12),第二个用于放大可编辑模式(minimumPressDuration = 0.5),第三个用于选择(minimumPressDuration = 0.8)。此解决方案删除了​​用于选择的 LongPressGestureRecognizer 和用于缩放编辑模式的第二个。

NSArray *textViewGestureRecognizers = self.captionTextView.gestureRecognizers;
NSMutableArray *mutableArrayOfGestureRecognizers = [[NSMutableArray alloc] init];
for (UIGestureRecognizer *gestureRecognizer in textViewGestureRecognizers) 
    if (![gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) 
        [mutableArrayOfGestureRecognizers addObject:gestureRecognizer];
     else 
        UILongPressGestureRecognizer *longPressGestureRecognizer = (UILongPressGestureRecognizer *)gestureRecognizer;
        if (longPressGestureRecognizer.minimumPressDuration < 0.3) 
            [mutableArrayOfGestureRecognizers addObject:gestureRecognizer];
        
    

self.captionTextView.gestureRecognizers = mutableArrayOfGestureRecognizers;

在 iOS 9 上测试,但它应该适用于所有版本(iOS 7、8、9)。 我希望它有帮助! :)

【讨论】:

可能不是最终解决方案,因为它可能会在未来的版本中中断,但目前唯一真正有效的解决方案。太好了!【参考方案4】:

这对我有用。

我无法摆脱放大镜,但这将允许您保持文本视图可选择(以便您可以点击链接),但摆脱所有与选择相关的 UI。仅在 iOS 9 上测试。

注意下面的斯威夫特!

首先,继承UITextView并包含这个函数:

override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool 
    return false

这将禁用复制等菜单。然后我包含一个设置方法,我从 init 调用它,我在其中执行一堆与设置相关的任务。 (我只使用故事板中的这些文本视图,因此解码器初始化):

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


private func setup() 
    selectable = true
    editable = false
    tintColor = UIColor.clearColor()

Selectable = true 保持链接可点击,editable = false 因为链接在可编辑文本视图中不可点击。指定一个明确的tintColor 会隐藏出现在所选内容开头和结尾的蓝条。

最后,在使用子类文本视图的控制器中,确保包含UITextViewDelegate 协议,设置委托textView.delegate = self,并实现此委托功能:

func textViewDidChangeSelection(textView: UITextView) 
    var range = NSRange()
    range.location = 0
    range.length = 0
    textView.selectedRange = range

如果没有此功能,选择栏和上下文菜单将被禁用,但您选择的文本后面仍会留下彩色背景。这个函数摆脱了选择背景。

就像我说的,我还没有找到摆脱放大镜的方法,但是如果他们在链接之外的任何地方长按,一旦放大镜消失,什么都不会留下。

【讨论】:

嗨@RyJ,你现在找到摆脱放大镜的方法了吗/谢谢 否,但我们已经改用 TTTAttributedLabel,github.com/TTTAttributedLabel/TTTAttributedLabel。没有放大镜,没有可选择的文本,并且摆脱了触发文本视图链接所需的长按。如果您需要显示带有内联链接的文本,则解决方案要好得多。【参考方案5】:

尽管面对未来可能的实施变化,它确实很脆弱,但 Kubík Kašpar 的方法是唯一对我有用的方法。

但是 (a) 如果您将 UITextView 子类化并且 (b) 如果您想要允许的唯一交互是链接点击,那么这可以变得更简单,您可以立即识别点击:

@interface GMTextView : UITextView
@end

@implementation GMTextView

- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 

  // discard all recognizers but the one that activates links, by just not calling super
  // (in iOS 9.2.3 a short press for links is 0.12s, long press for selection is 0.75s)

  if ([gestureRecognizer isMemberOfClass:UILongPressGestureRecognizer.class] &&
      ((UILongPressGestureRecognizer*)gestureRecognizer).minimumPressDuration < 0.25)   

    ((UILongPressGestureRecognizer*)gestureRecognizer).minimumPressDuration = 0.0;
    [super addGestureRecognizer:gestureRecognizer]; 
  


@end

【讨论】:

【参考方案6】:

这是一个 UITextView 子类方法,它只识别链接文本上的点。

class LinkTextView: UITextView 
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool 
        let tapLocation = point.applying(CGAffineTransform(translationX: -textContainerInset.left, y: -textContainerInset.top))
        let characterAtIndex = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        let linkAttributeAtIndex = textStorage.attribute(.link, at: characterAtIndex, effectiveRange: nil)

        // Returns true for points located on linked text
        return linkAttributeAtIndex != nil
    

    override func becomeFirstResponder() -> Bool 
        // Returning false disables double-tap selection of link text
        return false
    

【讨论】:

这会在您点击文本时突出显示文本。 有objc版本吗?【参考方案7】:

这几乎解决了文本选择被禁用并隐藏放大镜的问题——链接仍然有效。

func textViewDidChangeSelection(_ textView: UITextView) 
    if let gestureRecognizers = textView.gestureRecognizers 
        for recognizer in gestureRecognizers 
            if recognizer is UILongPressGestureRecognizer 
                if let index = textView.gestureRecognizers?.index(of: recognizer) 
                    textView.gestureRecognizers?.remove(at: index)
                
            
        
    

注意:您可以将识别器替换为所需的识别器,而不是删除。

【讨论】:

【参考方案8】:

Swift 4、Xcode 9.2

下面是一些不同的方法,

class TextView: UITextView 
    //MARK: Properties    
    open var didTouchedLink:((URL,NSRange,CGPoint) -> Void)?

    override init(frame: CGRect, textContainer: NSTextContainer?) 
        super.init(frame: frame, textContainer: textContainer)
    

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

    override func draw(_ rect: CGRect) 
        super.draw(rect)
    

    open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) 
        let touch = Array(touches)[0]
        if let view = touch.view 
            let point = touch.location(in: view)
            self.tapped(on: point)
        
    


extension TextView 
    fileprivate func tapped(on point:CGPoint) 
        var location: CGPoint = point
        location.x -= self.textContainerInset.left
        location.y -= self.textContainerInset.top
        let charIndex = layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        guard charIndex < self.textStorage.length else 
            return
        
        var range = NSRange(location: 0, length: 0)
        if let attributedText = self.attributedText 
            if let link = attributedText.attribute(NSAttributedStringKey.link, at: charIndex, effectiveRange: &range) as? URL 
                print("\n\t##-->You just tapped on '\(link)' withRange = \(NSStringFromRange(range))\n")
                self.didTouchedLink?(link, range, location)
            
        

    

如何使用,

let textView = TextView()//Init your textview and assign attributedString and other properties you want.
textView.didTouchedLink =  (url,tapRange,point) in
//here goes your other logic for successfull URL location

【讨论】:

【参考方案9】:

我发现lramirez135 和Kubík Kašpar 的答案几乎解决了这个问题。但是lramirez135 的答案无法处理长按选择,而Kubík Kašpar 的答案取决于iOS 版本。

我结合了他们的逻辑并在 swift 中创建了 UITextView 的这个子类,这对我有用。

class CustomUITextView: UITextView 
    override var canBecomeFirstResponder: Bool 
        return false
    

    init() 
        super.init(frame: .zero, textContainer: nil)

        guard let textViewGestureRecognizers = self.gestureRecognizers else  return 
        for textViewGestureRecognizer in textViewGestureRecognizers 
            if textViewGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) 
                textViewGestureRecognizer.isEnabled = false
            
        
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

Objective-c 版本:

@implementation CustomTextView

- (BOOL)canBecomeFirstResponder 
    return NO;


- (instancetype)init

    self = [super init];
    if (self) 
        self.backgroundColor = UIColor.whiteColor;
        self.textContainerInset = UIEdgeInsetsZero;
        self.textContainer.lineFragmentPadding = 0;
        self.editable = NO;
        self.scrollEnabled = NO;
        self.linkTextAttributes = @ NSForegroundColorAttributeName: UIColor.orangeColor ;

        NSArray<UIGestureRecognizer *> *textViewGestureRecognizers = (NSArray<UIGestureRecognizer *> *)self.gestureRecognizers;
        for (UIGestureRecognizer *textViewGestureRecognizer in textViewGestureRecognizers) 
            if ([textViewGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) 
                [textViewGestureRecognizer setEnabled:NO];
            
        
    
    return self;


@end

【讨论】:

以上是关于带有可点击链接但没有文本突出显示的 UITextView的主要内容,如果未能解决你的问题,请参考以下文章

突出显示链接

通过绑定动态创建的带有可点击链接的文本

如何在传入的文本中突出显示某些单词?

Android - ListView 没有收到带有可点击链接的文本视图的 OnItemClick

FullCalendar - 获取外部链接以突出显示现有事件

UITextField 突出显示所有文本