Detect Data in UITextView in UITableViewCell

爱⌒轻易说出口 提交于 2019-12-06 05:03:43

问题


I have a UITextView inside a UITableViewCell subclass. It has editable and userInteractionEnabled both set to NO. This allows the UITextView to detect data such as links and phone numbers. The data is detected correctly, but because userInteraction is disabled, it cannot respond to taps on that data. If I set userInteractionEnabled to YES, it works fine, but then the UITableViewCell cannot be selected since the UITextView swallows the touch.

I want to follow the link if the user taps on it, but I want didSelectRowAtIndexPath: to be called if the tap is on basic text.

I think the right approach is to subclass UITextView and pass touches to the cell, but I can't seem to find a way to detect whether or not the tap was on a link.

This is a similar question, but the answer will just pass all touches to the cell. I want to only pass the touches if they are NOT on a piece of detected data. issue enabling dataDetectorTypes on a UITextView in a UITableViewCell


回答1:


1. Using hitTest(_:with:)

UIView has a method called hitTest(_:with:) that has the following definition:

func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?

Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.

UITextView being a subclass of UIView, you can implement this method in any subclass of UITextView you may want to create.


The following Swift 3 example shows a UITableViewController that contains a single static UITableViewCell. The UITableViewCell embeds a UITextView. Any tap on a link inside the UITextView will launch Safari app; any tap on basic text inside the UITextView will trigger a push segue to the following UIViewController.

LinkTextView.swift

import UIKit

class LinkTextView: UITextView {

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

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

    func configure() {
        isScrollEnabled = false
        isEditable = false
        isUserInteractionEnabled = true
        isSelectable = true
        dataDetectorTypes = .link
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Get the character index from the tap location
        let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        // if we detect a link, handle the tap by returning self...
        if let _ = textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) {
            return self
        }

        // ... otherwise return nil ; the tap will go on to the next receiver
        return nil
    }

}

TextViewCell.swift

import UIKit

class TextViewCell: UITableViewCell {

    @IBOutlet weak var textView: LinkTextView!

}

TableViewController.swift

import UIKit

class TableViewController: UITableViewController {

    @IBOutlet weak var cell: TextViewCell!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Add text to cell's textView

        let text = "http://www.google.com Lorem ipsum dolor sit amet, consectetur adipiscing elit, http://www.yahoo.com sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
        cell.textView.text = text
    }

}


2. Using point(inside:with:)

As an alternative to override hitTest(_:with:), you can use point(inside:with:). point(inside:with:) has the following declaration:

func point(inside point: CGPoint, with event: UIEvent?) -> Bool

Returns a Boolean value indicating whether the receiver contains the specified point.


The following code shows how to implement point(inside:with:) instead of hitTest(_:with:) in your UITextView subclass:

LinkTextView.swift

import UIKit

class LinkTextView: UITextView {

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

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

    func configure() {
        isScrollEnabled = false
        isEditable = false
        isUserInteractionEnabled = true
        isSelectable = true
        dataDetectorTypes = .link
    }

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        // Get the character index from the tap location
        let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        // if we detect a link, handle the tap by returning true...
        if let _ = textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) {
            return true
        }

        // ... otherwise return false ; the tap will go on to the next receiver
        return false
    }

}

The complete project is available on this GitHub repo: LinkTextViewCell.

You can learn more about managing a UITextView inside a UITableViewCell by reading Swifty Approach to Handling UITextViews With Links in Cells.

You can learn more about the difference between hitTest(_:with:) and point(inside:with:) by reading Apple's Guide and Sample Code: "Event Delivery: The Responder Chain".




回答2:


I had the same issue. I solved it by subclassing UITextView and add a protocol :

@protocol SOTextViewDelegate;

@interface SOTextView : UITextView

@property (nonatomic, weak) id<SOTextViewDelegate> soDelegate;

@end

@protocol SOTextViewDelegate <NSObject>
@optional
- (void)soTextViewWasTapped:(SOTextView *)soTextview;

@end

In the implementation I've just added this :

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    if (self.selectedRange.length == 0 &&
        [_soDelegate respondsToSelector:@selector(soTextViewWasTapped:)]))

        [_soDelegate soTextViewWasTapped:self];
}

This delegate will tell the custom cell that the textView was tapped. My custom cell also have a delegate in order to trigger its actual selection.

Now you can tap on a link and it will open, you can tap on the textView and be notified and you still can select text and tap to deselect it.



来源:https://stackoverflow.com/questions/20507664/detect-data-in-uitextview-in-uitableviewcell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!