当前位置: > > > Swift - 让textview支持特殊标签点击响应(@标签、#标签)

Swift - 让textview支持特殊标签点击响应(@标签、#标签)

(本文代码已升级至Swift3)

有使用过新浪微博会发现,微博对发送的消息提供了几个特殊符号标签。
比如:输入“@+微博用户昵称(即ID)+空格或标点”,那么在这条微博中这个@条目就会表现成超链接形式,点击即可跳转到被@的某人的微博。
又比如话题标签“#+关键字+#”。表现形式就是点击后会跳转到包含该关键字的微博的搜索结果页面的超链接。
同样的,直接输入“http://hangge.com”那么微博中会显示为可点击的网页链接。

1,让textview支持特殊符号标签
通常情况下,这些带特殊标签符号内容对于多行文本框(UITextView)来说都是视为普通文本,要想让其支持点击,我们可以借助textview的URL检测功能来实现。
(1)首先遍历文本内容,将各种特殊文本替换成我们自定义的一些 URL scheme。比如:用 mention://hangge 表示 @hangge,用 hash://航歌 表示 #航歌#
(2)通过textview的 UITextFieldDelegate 代理的 shouldInteractWithURL 方法,我们可以捕获到这些自定义的 URL scheme 点击,然后通过判断 URL.scheme 来执行不同的操作。

2,先看效果图
(1)我们在页面上放置两个 textview,下面一个用来编辑文本。

(2)点击“发送”按钮则会将内容在上面一个textview中显示出来,可以看到特殊符号的字段都显示为可点击状态。

(3)测试下各个标签的点击响应事件。
         

3,实现步骤
(1)对于用来显示的textview,要将其 Detection Links(链接检测)打勾,去掉 Editable(使其不可编辑)。

(2)扩展UITextView:UITextFieldExtension.swift
import UIKit

extension UITextView {
    
    /**
     转换特殊符号标签字段
     */
    func resolveHashTags(){
        let nsText:NSString = self.text! as NSString
        // 使用默认设置的字体样式
        let attrs = [NSFontAttributeName : self.font!]
        let attrString = NSMutableAttributedString(string: nsText as String,
                                                   attributes:attrs)
        
        //用来记录遍历字符串的索引位置
        var bookmark = 0
        //用于拆分的特殊符号
        let charactersSet = CharacterSet(charactersIn: "@#")
        
        //先将字符串按空格和分隔符拆分
        let sentences:[String] = self.text.components(
            separatedBy: CharacterSet.whitespacesAndNewlines)
        
        for sentence in sentences {
            //如果是url链接则跳过
            if !verifyUrl(sentence as String) {
                //再按特殊符号拆分
                let words:[String] = sentence.components(
                    separatedBy: charactersSet)
                var bookmark2 = bookmark
                for i in 0..<words.count{
                    let word = words[i]
                    let keyword = chopOffNonAlphaNumericCharacters(word as String)
                    if keyword != "" && i>0{
                        //使用自定义的scheme来表示各种特殊链接,比如:mention:hangge
                        //使得这些字段会变蓝色且可点击
                        
                        //匹配的范围
                        let remainingRangeLength = min((nsText.length - bookmark2 + 1),
                                                       word.characters.count+2)
                        let remainingRange = NSRange(location: bookmark2-1,
                                                     length: remainingRangeLength)
                        print(keyword, bookmark2, remainingRangeLength)
                        
                        //获取转码后的关键字,用于url里的值
                        //(确保链接的正确性,比如url链接直接用中文就会有问题)
                        let encodeKeyword = keyword
                            .addingPercentEncoding(
                                withAllowedCharacters: CharacterSet.urlQueryAllowed)!
                        
                        //匹配@某人
                        var matchRange = nsText.range(of: "@\(keyword)",
                                                options: .literal,
                                                range:remainingRange)
                        attrString.addAttribute(NSLinkAttributeName,
                                                value: "mention:\(encodeKeyword)",
                                                range: matchRange)
                        
                        //匹配#话题#
                        matchRange = nsText.range(of: "#\(keyword)#",
                                    options: .literal,
                                    range:remainingRange)
                        attrString.addAttribute(NSLinkAttributeName,
                                                value: "hash:\(encodeKeyword)",
                                                range: matchRange)
                    }
                    //移动坐标索引记录
                    bookmark2 += word.characters.count + 1
                }
            }
            
            //移动坐标索引记录
            bookmark += sentence.characters.count + 1
        }
        
        print(nsText.length,bookmark)
        
        //最终赋值
        self.attributedText = attrString
    }
    
    /**
     验证URL格式是否正确
     */
    fileprivate func verifyUrl(_ str:String) -> Bool {
        // 创建一个正则表达式对象
        do {
            let dataDetector = try NSDataDetector(types:
                NSTextCheckingTypes(NSTextCheckingResult.CheckingType.link.rawValue))
            // 匹配字符串,返回结果集
            let res = dataDetector.matches(in: str,
                            options: NSRegularExpression.MatchingOptions(rawValue: 0),
                            range: NSMakeRange(0, str.characters.count))
            // 判断结果(完全匹配)
            if res.count == 1  && res[0].range.location == 0
                && res[0].range.length == str.characters.count {
                return true
            }
        }
        catch {
            print(error)
        }
        return false
    }
    
    /**
     过滤部多余的非数字和字符的部分
     比如:@hangge.123 -> @hangge
     */
    func chopOffNonAlphaNumericCharacters(_ text:String) -> String {
        let nonAlphaNumericCharacters = CharacterSet.alphanumerics.inverted
        let characterArray = text.components(separatedBy: nonAlphaNumericCharacters)
        return characterArray[0]
    }
}

(3)测试页面代码:
import UIKit

class ViewController: UIViewController, UITextViewDelegate {

    //展示文本框
    @IBOutlet weak var displayTextView: UITextView!
    //编辑文本框
    @IBOutlet weak var editTextView: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //设置展示文本框的代理
        displayTextView.delegate = self
    }

    //发送消息
    @IBAction func setMessage(_ sender: AnyObject) {
        //设置展示文本框的内容
        displayTextView.text = editTextView.text
        //对内容中的特殊标签字段做处理
        displayTextView.resolveHashTags()
        //清空输入框内容
        editTextView.text = ""
    }
    
    //展示文本框链接点击响应
    func textView(_ textView: UITextView, shouldInteractWith URL: URL,
                  in characterRange: NSRange) -> Bool {
        //判断URL scheme
        switch URL.scheme ?? "" {
        case "hash" :
            showAlert("hash", payload:
                (URL as NSURL).resourceSpecifier!.removingPercentEncoding!)
        case "mention" :
            showAlert("mention", payload:
                (URL as NSURL).resourceSpecifier!.removingPercentEncoding!)
        default:
            print("这个是普通的url链接")
        }
        
        return true
    }
    
    //显示消息
    func showAlert(_ tagType:String, payload:String){
        let alertController = UIAlertController(title: "检测到\(tagType)标签",
            message: payload, preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        self.present(alertController, animated: true, completion: nil)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
源码下载:hangge_1096.zip
评论4
  • 4楼
    2017-06-07 11:33
    派大星

    在swift 3.0 中,好多方法均已修改名称,比如:
    let nsText:NSString = self.text 这里报错 需要改为 let nsText:NSString = self.text as NSString
    let charactersSet = NSCharacterSet(charactersInString: "@#") 这里报错 需要改为 let charactersSet = NSCharacterSet(charactersIn: "@#")
    let words:[String] = sentence.components(separatedBy:
    NSCharacterSet.whitespacesAndNewlines)
    /**
    过滤部多余的非数字和字符的部分
    比如:@hangge.123 -> @hangge
    */
    func chopOffNonAlphaNumericCharacters(text:String) -> String {
    let nonAlphaNumericCharacters = NSCharacterSet.alphanumerics.inverted
    let characterArray = text.components(separatedBy: nonAlphaNumericCharacters)
    return characterArray[0]
    }
    在判断 if keyword != "" && i>0{}时候i总是=0 所以进不去,方法失效,@博主名,无法实现点击跳转;
    求解决

    站长回复

    多谢你的提醒,代码已更新,你可以再看下。

  • 3楼
    2016-04-28 14:33
    zhaohuikt@163.com

    尝试做了下 用tableview 仿微博列表,在cell里添加textview展示文本框,结果链接点击响应点击失效了,url.scheme和resourceSpecifier反馈为空,求解决

    站长回复

    我试了下在tableview里也是没问题的,你看看是不是因为链接是中文,如果是汉字的话需要转码,否则就会出现url.scheme为空的现象。

    我文章代码已更新,增加了转码,现在支持中文了。你可以再试试。

  • 2楼
    2016-04-14 14:00
    iOS developer

    好棒 。 真的喜欢

    站长回复

    很高兴你能喜欢,欢迎常来看看。

  • 1楼
    2016-03-31 00:19
    ylpcode

    很棒的一个网站~站长加油!!

    站长回复

    谢谢你的夸奖,欢迎常来看看,我会持续更新下去的。