Search code examples
iosswiftuitableviewuitextviewtttattributedlabel

How to use UITableView and NSLink together in Swift iOS?


Extension Code:

extension String {    
func accentTagAndLink(tags:Array<String>) -> NSMutableAttributedString {

    let attributedString:NSMutableAttributedString = NSMutableAttributedString(string: self)
    var NSContents = self as NSString

    for tagChild in tags {
        if !tagChild.starts(with: "<") {
            let range = NSContents.range(of: "#" + tagChild)
            var changeString = ""

            for _ in 0..<tagChild.count {
                changeString += "$"
            }

            NSContents = NSContents.replacingOccurrences(of: tagChild, with: changeString, options: .literal, range: range) as NSString
            attributedString.addAttribute(.link, value: tagChild, range: range)
            attributedString.addAttribute(.foregroundColor, value: UIColor(red: 79/255, green: 205/255, blue: 255/255, alpha: 1), range: range)
        }
    }

    return attributedString
}
}

TableViewCell :

class PostCell: TableViewCell {
    @IBOutlet weak var postContent: UITextView!
}

in mainViewController

class PostViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,UITextViewDelegate {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = postTableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
        let post = postList[indexPath.row]
        let tags = (post["Content"] as! String).FindTagAtString()
        cell.postContent.delegate = self
        cell.postContent.isSelectable = true
        cell.postContent.isEditable = false
        cell.postContent.isUserInteractionEnabled = true
        cell.postContent.dataDetectorTypes = UIDataDetectorTypes.link
        cell.postContent.attributedText = (post["Content"] as! String).accentTagAndLink(tags: tags)
        let tap = MyTapGesture(target: self, action: #selector(self.tapTextView))
            tap.indexPath = indexPath
        cell.postContent.addGestureRecognizer(tap)
    }

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        print(URL)
        return true
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("cell tap")
        movePostDetail(indexPath)
    }
    @objc func tapTextView(sender:MyTapGesture){
        print("cell tap")
        movePostDetail(sender.indexPath)

    }
}

MyTapGesture :

class MyTapGesture: UITapGestureRecognizer {
    var indexPath:IndexPath!
}

Content is NormalText + TagText

If tap NormalText to run movePostDetail and if tap TagText to run print(URL) but always movePostDetail when addGestureRecognizer

How to use together

It worked well when I took TTTAttributedLabel, but because of crash so i haven't used TTTAttributedLabel.


Solution

  • I advice you to use a CUSTOM ATTRIBUTE NAME and handle the tap by yourself.

    Solution in Swift 4

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        let tapGes = UITapGestureRecognizer(target: self, action: #selector(TableViewCell.handleTap(sender:)))
        contentTf.addGestureRecognizer(tapGes)
    
        let attributeName = "CustomAttributeName"
        let attStr = NSAttributedString(string: "https", attributes: [NSAttributedStringKey(rawValue: attributeName):"https"])
    
        contentTf.attributedText = attStr
    }
    @objc func handleTap(sender: UITapGestureRecognizer) -> Void {
        let myTextView = sender.view as! UITextView
        let layoutManager = myTextView.layoutManager
    
        // location of tap in myTextView coordinates and taking the inset into account
        var location = sender.location(in: myTextView)
        location.x -= myTextView.textContainerInset.left;
        location.y -= myTextView.textContainerInset.top;
    
        // character index at tap location
        let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    
        // if index is valid then do something.
        if characterIndex < myTextView.textStorage.length {
    
            // print the character index
            print("character index: \(characterIndex)")
    
            // print the character at the index
            let myRange = NSRange(location: characterIndex, length: 1)
            let substring = (myTextView.attributedText.string as NSString).substring(with: myRange)
            print("character at index: \(substring)")
    
            // check if the tap location has a certain attribute
            let attributeName = "CustomAttributeName"
            let attributeValue = myTextView.attributedText.attribute(NSAttributedStringKey(rawValue: attributeName), at: characterIndex, effectiveRange: nil) as? String
            if let value = attributeValue {
                print("You tapped on \(attributeName) and the value is: \(value)")
            }
    
        }
    }
    

    TEST

    character index: 3
    character at index: p
    You tapped on CustomAttributeName and the value is: https