在 UITextView 中以大写形式显示文本并从 shouldChangeTextIn 中获取新文本,以保持原始文本和输入文本的大小写

Posted

技术标签:

【中文标题】在 UITextView 中以大写形式显示文本并从 shouldChangeTextIn 中获取新文本,以保持原始文本和输入文本的大小写【英文标题】:Showing Text in UITextView with upppercase and getting new text from shouldChangeTextIn keeping case of original and entered text 【发布时间】:2018-05-18 20:12:54 【问题描述】:

我有一个 UITextView,有时我需要以全部大写形式显示,有时以原始情况显示。这部分很简单,但是当用户开始编辑文本时,我需要使用更改更新原始字符串,同时保留字符串的原始大小写。出于这个原因,我无法使用 textDidChange 而是使用 shouldChangeTextIn 并更改原始字符串。大多数情况下,一切都按预期工作,除非选择了多个单词并且您在键盘上点击预测词。可能还有其他事情会破坏。解决此问题以在显示变异字符串的同时保留原始字符串的最佳方法是什么。这是一个最小的例子。

import UIKit

class ViewController: UIViewController 

    lazy var textView : UITextView = 
        let txtv = UITextView(frame: CGRect(x: 0, y:40, width: self.view.frame.width, height: 200))
        txtv.autoresizingMask = [.flexibleWidth,.flexibleHeight]
        txtv.delegate = self
        txtv.font = UIFont.systemFont(ofSize: 22)
        return txtv
    ()

    lazy var button : UIButton = 
        let btn = UIButton(frame: CGRect(x: 0, y:250, width: self.view.frame.width - 40, height: 50))
        btn.autoresizingMask = [.flexibleWidth,.flexibleHeight]

        btn.setTitleColor(UIColor.blue, for: .normal)
        btn.setTitle("SHOW TEXT", for: .normal)
        btn.addTarget(self, action: #selector(changeState), for: .touchUpInside)
        return btn
    ()

    var textString = "This is a test to see how we are doing"
    var shouldCapitalize : Bool = true
    private var hack_shouldIgnorePredictiveInput = false

    override func viewDidLoad() 
        super.viewDidLoad()
        self.view.addSubview(textView)
        self.view.addSubview(button)
        //get things going
        updateTextView()
    

    func updateTextView()
        //we could change font size here or size of textview
        var textToShow = self.textString
        if shouldCapitalize == true
            textToShow = textToShow.uppercased()
        
        textView.text = textToShow
        print("the text string we really care about is::::: \(textString)")
    

    @objc func changeState()
        shouldCapitalize = !shouldCapitalize
        updateTextView()
    


extension ViewController : UITextViewDelegate

    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool 

        if hack_shouldIgnorePredictiveInput 
            hack_shouldIgnorePredictiveInput = false
            return false
        

        hack_shouldIgnorePredictiveInput = true

        //how do i replace the characters or text changging without changing the case of the original string
        print("the textview text is \(textView.text)")
        print("the textview text is \((textView.text as NSString).length)")
        print("the range is \(range)")
        print("the text is \(text)")

        let selectedRange = self.textView.selectedRange

        if let str = textView.text as? NSString
            if range.length == 0 && text == ""
                print("the ext is empty")

                textString = ""
                updateTextView()
            
            if let tracker = textString as? NSString
                if range.location == tracker.length
                    print("adding")
                    print("the lenght of the tracker is \(tracker.length)")
                    let newRange = NSMakeRange(tracker.length, range.length)
                    print("the new range is \(newRange)")
                    let newString = tracker.replacingCharacters(in: newRange, with: text)
                    print("the new text is \(newString)")

                    textString = newString
                    updateTextView()
                else if range.location < tracker.length
                    let newString = tracker.replacingCharacters(in: range, with: text)
                    print("the new text is \(newString)")

                    textString = newString
                    updateTextView()
                    if (newString as NSString).length > tracker.length
                        print("setting cursor \(NSMakeRange(range.location + range.length, 0))")
                        self.textView.selectedRange = NSMakeRange(range.location + range.length + 1, 0)
                    else
                        self.textView.selectedRange = NSMakeRange(range.location, 0)
                    



                else
                   //the problem seems to be in here
                    print("maybe adding")
                    print("the lenght of the tracker is \(tracker.length)")
                    let newRange = NSMakeRange(tracker.length, range.length)
                    print("the new range is \(newRange)")
                    let newString = str.replacingCharacters(in: newRange, with: text)
                    print("the new text is \(newString)")
                    textString = newString
                    updateTextView()
                
            
        

        hack_shouldIgnorePredictiveInput = false
        return false
    


【问题讨论】:

您最后的 else 语句的注释为 //the problem seems to be in here。只有range.location 大于tracker.length 因为其他if...else 已经捕获了其他情况,您才会进入其他情况。似乎让range.location 大于tracker.length 的唯一方法是将textString = "" 设置在您检查range.length == 0 &amp;&amp; text == "" 的上方。我不太明白这一切。也许您可以提供打印语句的输出以获得更多上下文。为每个打印语句提供唯一的文本以避免歧义。 @jimmyg 你帮我指出我的最后一个 else 不应该是必要的,当我写它时我认为它是不需要的。它被调用的原因是 textview 试图添加一个带有预测文本的空格,我拦截了它并将字符串设置为空,这使得最后一个 else 被命中。我想我现在明白了,但感谢你触发了我的直觉。 【参考方案1】:

因此,似乎我的底部 else 被调用了,因为文本被设置为空,这是由于有时会调用委托两次以输入空格这一事实驱动的。来自 jimmyg 的评论帮助我看到他是对的,最后一个 else 永远不应该被击中,这是我在编写函数时的想法。幸运的是,answer 帮助解释了为什么会出现空文本。如果您在这里,我建议您投票赞成该答案。我还建议对这个answer 进行投票,这有助于阻止一些带有预测文本的不必要的调用,并且可以在我的代码中看到。最后,所有逻辑似乎都已到位,并且更加简单,并且在 UITextView 内的预期文本和转换后的文本之间切换正在工作。

import UIKit

class ViewController: UIViewController 

    lazy var textView : UITextView = 
        let txtv = UITextView(frame: CGRect(x: 0, y:40, width: self.view.frame.width, height: 200))
        txtv.autoresizingMask = [.flexibleWidth,.flexibleHeight]
        txtv.delegate = self
        txtv.font = UIFont.systemFont(ofSize: 22)
        return txtv
    ()

    lazy var button : UIButton = 
        let btn = UIButton(frame: CGRect(x: 0, y:250, width: self.view.frame.width - 40, height: 50))
        btn.autoresizingMask = [.flexibleWidth,.flexibleHeight]
        btn.setTitleColor(UIColor.blue, for: .normal)
        btn.setTitle("SHOW TEXT", for: .normal)
        btn.addTarget(self, action: #selector(changeState), for: .touchUpInside)
        return btn
    ()

    var textString = "This is a test to see how we are doing"
    var shouldCapitalize : Bool = true
    private var hack_shouldIgnorePredictiveInput = false

    override func viewDidLoad() 
        super.viewDidLoad()
        self.view.addSubview(textView)
        self.view.addSubview(button)
        //get things going
        updateTextView()
    

    func updateTextView()
        //we could change font size here or size of textview
        var textToShow = self.textString
        if shouldCapitalize == true
            textToShow = textToShow.uppercased()
        
        textView.text = textToShow
        print("realString:: \(textString)")
        print("textView showing:: \(textView.text)")
    


    @objc func changeState()
        shouldCapitalize = !shouldCapitalize
        updateTextView()
    


extension ViewController : UITextViewDelegate

    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool 

        if hack_shouldIgnorePredictiveInput 
            hack_shouldIgnorePredictiveInput = false
            return false
        

        hack_shouldIgnorePredictiveInput = true

            if range.length == 0 && text == "" && self.textString.count > 0 && range.length == self.textString.count
                textString = ""
                updateTextView()
            
            let tracker = (textString as NSString)
            if range.location == tracker.length
                let newRange = NSMakeRange(tracker.length, range.length)
                let newString = tracker.replacingCharacters(in: newRange, with: text)
                textString = newString
                updateTextView()
            else if range.location < tracker.length
                if text.isEmpty && range.length == 0 && range.location > 0
                    let newString = tracker.replacingCharacters(in:range, with: text)
                    textString = newString
                    updateTextView()

                    self.textView.selectedRange = NSMakeRange(range.upperBound, 0)
                else
                    let newString = tracker.replacingCharacters(in: range, with: text)
                    textString = newString
                    updateTextView()
                   self.textView.selectedRange = NSMakeRange(range.upperBound - tracker.length + (newString as NSString).length , 0)
                
            

        hack_shouldIgnorePredictiveInput = false
        return false
    

【讨论】:

以上是关于在 UITextView 中以大写形式显示文本并从 shouldChangeTextIn 中获取新文本,以保持原始文本和输入文本的大小写的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 UITextView 中以编程方式选择文本吗?

c_cpp 管道示例:从进程中读取字符并在另一个进程中以大写形式显示

在 UITextView 中显示带有超大首字母的文本块

如何在android中以小写显示文本?

如何在UITextView中以编程方式移动光标?

解除键盘后,带有所选文本的UITextView不响应触摸