I have a UITextView that I need to sometimes show in all caps and sometimes in original case. This part is easy but when the user starts editing text I need to update the original string with the changes while keeping the original case of the string. For this reason I have not been able to use textDidChange and instead I am using shouldChangeTextIn and changing the original string. Mostly everything is working as expected except if multiple words are selected and you use a tap on a predictive word in the keyboard. There may be other things that break. What is the best way to fix this to keep the original string while showing a mutated string. Here is a minimal example.
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
}
}