代码之家  ›  专栏  ›  技术社区  ›  Alexander Khitev

从Unicode字符符号中获取范围。迅捷

  •  2
  • Alexander Khitev  · 技术社区  · 6 年前

    我用 textView(_: shouldChangeTextIn: replacementText:) 函数根据情况更改输入数据。我使用范围,但在使用Unicode字符符号(例如 (2055年) )拜托,告诉我怎么做?

     func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    
            let maxLenthNotReached = textView.text.count + (text.count - range.length) <= maxTextLength
    
            if maxLenthNotReached {
                guard let newRange = Range(range, in: identityString) else { return false }
                identityString = identityString.replacingCharacters(in: newRange, with: text)
            }
    
            return maxLenthNotReached
        }
    

    Example project

    应用程序崩溃示例 http://take.ms/ojIJq

    更新 :我更改了此方法,但删除时再次崩溃

    "entering data" ""
    "testString" "༼ つ ͡° ͜ʖ🇺🇸 ͡° ༽つ( ͡° ͜ʖ🏎 ͡"
    "entering data" ""
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        debugPrint("textView.text", textView.text)
        testString = textView.text.replacingCharacters(in: Range(range, in: textView.text)!, with: text)//
        debugPrint("testString", testString)
        return true
    }
    

    更新1 :我在文本视图中输入了这些字符

    ( ͡° ͜ʖ ͡🇺🇸°)༼ つ ͡° ͜ʖ ͡🏎° ༽つ
    

    然后我开始删除字符,在删除三个右边的几个符号后,从右向左 ° ༽つ ,而车的表情符号已经离开,那么我就无法得到范围,因为我把卫士和应用程序不崩溃,如果我删除了那当然会有应用程序崩溃。

    全码

    class ViewController: UIViewController {
    
        // MARK: - IBOutlets
        @IBOutlet private weak var textView: UITextView! {
            didSet {
                textView.delegate = self
                textView.text = "( ͡° ͜ʖ ͡🇺🇸°)༼ つ ͡° ͜ʖ ͡🏎° ༽つ"
            }
        }
    
        // MARK: - Properties
    
        private var testString = ""
    
    }
    
    
    extension ViewController: UITextViewDelegate {
    
        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            guard let newRange = Range(range, in: textView.text) else {
                return false
            }
            testString = textView.text.replacingCharacters(in: newRange, with: text)
            return true
        }
    
    }
    

    更新2 :在与martin交谈之后,我发现并提供了一个细节,即这个问题只在google键盘上发生,而在默认键盘上,一切正常。

    我原来的台词是 "( ͡° ͜ʖ ͡🇺🇸°)༼ つ ͡° ͜ʖ ͡🏎° ༽つ” ,这一行是一个例子。如果我开始从左到右删除这一行,我会得到应用程序崩溃,martin要求在应用程序崩溃前在控制台中显示最新数据,崩溃前的最后一次打印是 textView" "( ͡° ͜ʖ ͡🇺🇸°)༼ つ ͡° ͜ʖ ͡🏎" "range" {27, 1}

    1 回复  |  直到 6 年前
        1
  •  3
  •   Martin R    6 年前

    在讨论中发现:

    • OP正在使用谷歌键盘,
    • 文本视图委托方法是用

      textView.text = "( ͡° ͜ʖ ͡🇺🇸°)༼ つ ͡° ͜ʖ ͡🏎"
      range = { 27, 1 }
      
    • 然后

      let newRange = Range(range, in: textView.text)
      

      收益率 nil .

    原因是范围指向 🏎 字符, 存储为一个utf-16代理项对。这是一个简化的自包含 例子:

    let text = "Hello 🏎!"
    let range = NSRange(location: 7, length: 1)
    let newRange = Range(range, in: text)
    print(newRange as Any) // nil   😭😭
    

    这看起来像个bug(在google键盘上?)对我来说,但有一个可能的解决办法。

    诀窍是确定最接近的 字符序列,这是如何做到的 (比较) From any UTF-16 offset, find the corresponding String.Index that lies on a Character boundary ):

    extension String {
        func safeRange(from nsRange: NSRange) -> Range<String.Index>? {
            guard nsRange.location >= 0 && nsRange.location <= utf16.count else { return nil }
            guard nsRange.length >= 0 && nsRange.location + nsRange.length <= utf16.count else { return nil }
            let from = String.Index(encodedOffset: nsRange.location)
            let to = String.Index(encodedOffset: nsRange.location + nsRange.length)
            return rangeOfComposedCharacterSequences(for: from..<to)
        }
    }
    

    现在

    let newRange = textView.text.safeRange(from: range)
    

    返回A String 包围整个区域的范围 γ射线 性格。在我们 简化示例:

    let text = "Hello 🏎!"
    let range = NSRange(location: 7, length: 1)
    let newRange = text.safeRange(from: range)
    print(newRange as Any) // Optional(Range(...))   😀😀
    print(text.replacingCharacters(in: newRange!, with: "🚂")) // Hello 🚂!