iOS UITextView 实现类似微博的话题、提及用户效果

2022-06-07 15:56:53
目录
#话题#1.高亮2.点击事件@提及用户1.高亮2.整体删除

最近接了一个需求,在发布动态的时候,增加类似微博的#话题#@提及用户的效果,在此做一简要记录。

#话题#

最终效果是:

    编辑过程中#话题内容#实时高亮

      高亮部分可以响应点击事件

      1.高亮

      基本思路是:使用正则匹配出成对的#,再利用UITextView的富文本实现高亮效果。

      func refreshTopicStyle() {
              
              let regex = try! NSRegularExpression(pattern: "此处填写正则表达式",
                                                   options:[NSRegularExpression.Options.caseInsensitive])
              // 注意点
              let totalRange = NSMakeRange(0, (inputTextView.attributedText.string as NSString).length)
              let results = regex.matches(in: inputTextView.attributedText.string,
                                          options: NSRegularExpression.MatchingOptions.init(rawValue: 0),
                                          range: totalRange)
              let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: inputTextView.attributedText.string)
              attributedString.setAttributes(normalAttributes, range: totalRange)
              
              for result in results {
                  attributedString.setAttributes(topicAttributes, range: result.range)
              }
              inputTextView.attributedText = attributedString
          }

      这有一个注意点,计算 totalRange 前,先将 String 转成了 NSString,这是因为此处 NSRange 中的 length 需要的是 UTF-16 长度,也就是与 NSString 的 length 定义一致,而 Swift 中的 String 没有 length 只有 count,指的是字符数,当文本中出现 emoji 表情时,二者就不一致了。

      当然,也有一些其他办法来处理,如:

      let lengthA = inputTextView.textStorage.length
      let lengthB = inputTextView.attributedText.string.utf16.count

      2.点击事件

      实现高亮部分的点击事件,目前有3种实现方案:

        直接给UITextView添加点击事件通过设置LinkAttribute,利用超文本链接的点击实现重写UITextViewtouches...方法

        其中,第二种只限于在非编辑状态(即>textView.isEditable = false)下的点击,故排除,①、③均可,本文采用第一种,主要实现如下:

        inputTextView.addTapGesture(self, handler: #selector(tapAttributedText(tap:)))
        @objc private func tapAttributedText(tap: UITapGestureRecognizer) {
            guard tap.isKind(of: UITapGestureRecognizer.self), let textView = tap.view as? UITextView else {
                return
            }
            let layoutManager = textView.layoutManager
            var tapLocation = tap.location(in: textView)
            tapLocation.x -= textView.textContainerInset.left
            tapLocation.y -= textView.textContainerInset.top
            let characterIndex = layoutManager.characterIndex(for: tapLocation,
                                                              in: textView.textContainer,
                                                              fractionOfDistanceBetweenInsertionPoints: nil)
            for result in getCheckResult(format: Constants.TopicRegularExpression, text: inputTextView.attributedText.string) {
                if result.range.location < characterIndex, characterIndex < result.range.location + result.range.length {
                    // 此处响应点击事件
                    MBProgressHUD.showOnlyText(to: self.view, title: "美好时光")
                    return
                }
            }
            inputTextView.becomeFirstResponder()
        }

        @提及用户

          编辑过程中>@提及用户 实时高亮,且只允许选取的用户名高亮,手动输入不高亮;

            点击删除键的时候,一次性删除整个高亮部分

            1.高亮

              记录位置

              本来准备用正则匹配的,但因为只允许选取的用户名高亮,纯手动输入的不高亮,所以使用正则匹配就不合理了,这里采用实时记录、更新已选取用户名位置的方式实现。

              /// 用来保存已选取用户信息的结构体
              struct UserInfo {
                  /// 用户名
                  var userName: String
                  /// 位置信息
                  var range: NSRange
                  /// 用于临时替换的等长字符串
                  var placeholder: String
              }
                临时替换

                因为#话题#@提及用户可以同时存在,所以需要考虑可能互相影响的问题,比如@提及用户中间可能出现#,导致前后话题的正则匹配发生错乱。

                解决方案是:先使用一个@开头且与@提及用户等长的字符串替换@提及用户,再执行#话题#的正则匹配,最后再换回来。

                2.整体删除

                删除操作分为两步:第一次点删除仅选中整个用户名(提醒用户是整体删除);第二次点删除才真的删除文本。

                func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
                    if text == "" {
                        for (num, user) in usersArray.enumerated() { // usersArray 用于存放已选取的用户信息
                            // ②删除选中的用户名
                            if textView.selectedRange.location <= user.range.location && NSMaxRange(user.range) <= NSMaxRange(textView.selectedRange) {
                                textView.replace(textView.selectedTextRange ?? UITextRange(), withText: "")
                                return false
                            }
                            // ①选中用户名
                            if textView.selectedRange.length == 0 && (textView.selectedRange.location == user.range.location + user.range.length) {
                                textView.selectedRange = user.range
                                return false
                            }
                        }
                    }
                }

                到此这篇关于iOS UITextView 实现类似微博的话题、提及功能的文章就介绍到这了,更多相关iOS UITextView微博话题内容请搜索易采站长站以前的文章或继续浏览下面的相关文章希望大家以后多多支持易采站长站!