Post

emoji计算长度

在处理包含emoji的代码进行截取时,遇到了崩溃,调查原因是计算截取文字位置时,Objective-C里的NSStringswift里的String不一致
以下为同事研究总结的内容

跟大家分享一点关于字符串长度的知识点

  1. OC中的NSString.length与Swift中String.count并不一定相等
  2. NSString.length返回的是字符串utf16编码的长度, String.count返回的是unicode码的个数
  3. 因此 String.utf16.count才等于NSString.length
  4. textview.selectedRange.location获取到的光标位置, 是以utf16编码的长度为计数单位的, 因此设计到取光标位置的时候, 字符串的操作都应该用长度来计数, 而非String.count
  5. 一个emoji表情的length通常是2, 组合型emoji如👨‍👩‍👧‍👧可能超过10, 但是体现在String.count里都是1

案例一:UITextView获取selectedRange.location里,按range截取崩溃的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
extension String {
     subscript (range: Range<Int>) -> String {
        get {
            var fromOffset = range.lowerBound
            if range.lowerBound > self.count {
                fromOffset = self.count
            }
            var toOffset = range.upperBound
            if range.upperBound > self.count {
                toOffset = self.count
            }
            let startIndex = self.index(self.startIndex, offsetBy: fromOffset)
            let endIndex = self.index(self.startIndex, offsetBy: toOffset)
            return String(self[startIndex..<endIndex])
        } set {
            let startIndex = self.index(self.startIndex, offsetBy: range.lowerBound)
            let endIndex = self.index(self.startIndex, offsetBy: range.upperBound)
            let strRange = startIndex..<endIndex
            self.replaceSubrange(strRange, with: newValue)
        }
    }
}
textView.text // "did😗🙂"
// swift里String的count、OC里NSString的length的区别
textView.text.length // 5
textView.text.count // 5
(textView.text as NSString).length // 7
// 获取textView里的range
textView.selectedRange // {location:7, length:0} ,这里的7就是因为把emoji按2个长度计算了
let cursorLoc = textView.selectedRange.location // 7
let frontText = textView.text[0..<cursorLoc] // ❌❌❌会崩溃,因为截取的字符串长度比原始字符串还长❌❌❌
let frontText2 = (textView.text as NSString).substring(with: NSMakeRange(0, cursorLoc))

案例二:使用NSMutableAttributedString设置属性时,swift里Stringrange(of: xxx)方法与oc里NSStringrange(of: xxx)方法结果不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let text = "did😗🙂"
let ocText = (text as NSString)
if let findRange = text.range(of: "did😗") {
   let start = text.distance(from: text.startIndex, to: findRange.lowerBound)
   let length = text.distance(from: findRange.lowerBound, to: findRange.upperBound)
   let swiftRange = NSMakeRange(start, length) // {0, 4}
   let attStr = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.foregroundColor: UIColor.black])
   attStr.addAttribute(.foregroundColor, value: UIColor.red, range: swiftRange)
   
   let label = UILabel()
   label.attributedText = attStr
   label.frame = CGRect(x: 100, y: 300, width: 220, height: 40)
   self.view.addSubview(label)
 }

 let ocRange = ocText.range(of: "did😗") // {0, 5}
   let label2 = UILabel()
   let attStr2 = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.foregroundColor: UIColor.black])
   attStr2.addAttribute(.foregroundColor, value: UIColor.red, range: ocRange)
   label2.attributedText = attStr2
   label2.frame = CGRect(x: 100, y: 350, width: 220, height: 40)
   self.view.addSubview(label2)

image image


遍历查询所有符合条件的range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
func test() {
    var startIndex: Int = 0
    let text = "did😗🙂did😗addfdid😗"
    var ocText: NSString = text as NSString
    var findStr1 = "did😗"
    var findRange = self.findAllRange(ocText: ocText, startIndex: startIndex, findStr1: findStr1)
    while findRange.length > 0 {
        startIndex = findRange.location + findRange.length
        findRange = self.findAllRange(ocText: ocText, startIndex: startIndex, findStr1: findStr1)
        if findRange.length <= 0 {
            break
        }
    }
    let rangeList = text.findAllRange(findStr: findStr1)
}
func findAllRange(ocText: NSString, startIndex: Int, findStr1: String) -> NSRange {
     if startIndex >= ocText.length {
         return NSRange(location: 0, length: 0)
     }
     let subLen = ocText.length - startIndex
     if subLen <= 0 {
         return NSRange(location: 0, length: 0)
     }
     // location: 包含要截取的第一个, length: 从location开始要截取的长度
     let ocSubStr = (startIndex == 0) ? ocText : (ocText.substring(with: NSRange(location: startIndex, length: subLen)) as NSString) 

     var findRange = ocSubStr.range(of: findStr1)
     if findRange.length <= 0 {
         return NSRange(location: 0, length: 0)
     }
     let resultRange = NSRange(location: startIndex+findRange.location, length: findRange.length)
     return resultRange
 }
// 使用分类的方式实现
extension String {
    func findAllRange(findStr: String) -> [NSRange] {
         var ocText: NSString = (self as NSString)
         
        var rangeList: [NSRange] = []
        var startIndex: Int = 0
        // 查找第一遍
        var findRange = self.findStrRange(ocText: ocText, startIndex: startIndex, findStr: findStr)
        
        while findRange.length > 0 {
            // 如果找到了,继续遍历查找
            rangeList.append(findRange)
            startIndex = findRange.location + findRange.length
            findRange = self.findStrRange(ocText: ocText, startIndex: startIndex, findStr: findStr)
            if findRange.length <= 0 {
                // 查找结束后,结束遍历
                break
            }
        }
        return rangeList
     }
    func findStrRange(ocText: NSString, startIndex: Int, findStr: String) -> NSRange {
         if startIndex >= ocText.length {
             return NSRange(location: 0, length: 0)
         }
         let subLen = ocText.length - startIndex
         if subLen <= 0 {
             return NSRange(location: 0, length: 0)
         }
         // location: 包含要截取的第一个, length: 从location开始要截取的长度
         let ocSubStr = (startIndex == 0) ? ocText : (ocText.substring(with: NSRange(location: startIndex, length: subLen)) as NSString)

         var findRange = ocSubStr.range(of: findStr)
         if findRange.length <= 0 {
             return NSRange(location: 0, length: 0)
         }
         let resultRange = NSRange(location: startIndex+findRange.location, length: findRange.length)
         return resultRange
     }
}

文字匹配找到的信息的遇到range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let text = "@asdf123😈😄👪你好 是的👍🏻 @啦🌶sdf 水电费撒旦法"
let regex = try NSRegularExpression(pattern: "@\\S+")
// 在文本中查找匹配项
let matches = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
// 对所有的@信息进行处理
for result in matches {
    if result.range.length > 1 {
        // 崩溃 result.range.length比实际的字符长度大
        let fromOffset = result.range.location
        let toOffset = fromOffset + result.range.length
        let startIndex = self.index(self.startIndex, offsetBy: fromOffset)
        let endIndex = self.index(self.startIndex, offsetBy: toOffset)
        // 由于emoji长度有问题,会导致崩溃
       let findText1 = text[startIndex..<endIndex]
       // 不崩溃
       let findText2 = (text as NSString).substring(with: NSMakeRange(result.range.location, result.range.length))

    }
}
This post is licensed under CC BY 4.0 by the author.