0

I want to restrict what is entered into a UITextView to Doubles, each Double separated by a space. Of course this means that only one decimal point is allowed.

The following code removes letters and symbols, but does not work for decimals. After entering a decimal point, the next character typed deletes the decimal point.

What am I doing wrong???

import UIKit

class ViewController: UIViewController, UITextViewDelegate {

    @IBOutlet weak var dataInputField: UITextView!

    var currentEntryHasDecimalPoint:Bool = false
    var validChars: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", " "]


    override func viewDidLoad() {
        super.viewDidLoad()
        dataInputField.delegate = self
    }


    func textViewDidChange(_ textView: UITextView ) {

        if let str = dataInputField.text, !str.isEmpty {

            let validChar:Bool = Set(str).isSubset(of: validChars)
            if validChar {

                let newChar = str.last!
                switch newChar {
                case ".":
                    currentEntryHasDecimalPoint = true
                    validChars.remove(".")
                case " ":
                    currentEntryHasDecimalPoint = false
                    validChars.insert(".")

                default:
                    print("default")
                }
            }

            else {
                dataInputField.deleteBackward()
            }
        }
    }

}
Mattie
  • 2,465
  • 2
  • 23
  • 31
Greg
  • 495
  • 4
  • 12
  • You are assuming the user is simply typing single characters. What happens if they copy and paste? Also this will have a rather annoying effect for the user to type a character only to have it deleted. You might be better to look at textView(shouldChangetextIn:replacementText:). As a matter of style, textViewDidChange already passes the textView as a parameter, why not use that rather than a class field member? – Dale Jan 07 '19 at 04:16
  • I suggest you use `textView(_:shouldChangeTextIn:replacementText:)` instead of `textViewDidChange` – Zonily Jame Jan 07 '19 at 06:13
  • Had't got to copy and paste yet, just trying to figure this one out. Deleting the offending character happens so quickly that it simply appears to just be ignored. When typing a letter into a list of numbers, the user should not really expect a letter to be allowed. – Greg Jan 10 '19 at 17:48
  • @Zonily Jame, can you show me how to use that? (I am very new to Swift.) – Greg Jan 10 '19 at 17:49
  • This [so answer](https://stackoverflow.com/a/32935626/5928180) and this [so answer](https://stackoverflow.com/a/44178930/5928180) can help you – Zonily Jame Jan 11 '19 at 06:38
  • @Zonily Jame, I see no end of that function - it's how to use it that I am struggling with. – Greg Jan 11 '19 at 21:44

1 Answers1

1

Since deleteBackward() does make change to the textView, it also trigger textViewDidChange. As "." has already been removed from validChars when you input "." followed by a letter, the letter will be deleted and trigger textViewDidChange, then the "." will be deleted. You should validate the text using the Regular Expression /^(?:[0-9]+(?:\.$|\.[0-9]+)?(?:\s+|\s*$))+$/ instead of using Set operations.

func textViewDidChange(_ textView: UITextView ) {

    if let str = textView.text,
        !str.isEmpty,
        let regex = try? NSRegularExpression(pattern: "^(?:[0-9]+(?:\\.$|\\.[0-9]+)?(?:\\s+|\\s*$))+$", options: []),
        regex.numberOfMatches(in: str, options: [], range: NSRange(location: 0, length: str.count)) == 0
    {
        textView.deleteBackward()
    }
}
Ricky Mo
  • 3,165
  • 1
  • 7
  • 17
  • This solution will fail in many cases. It doesn't handle a user pasting text in the middle of the text view. It assumes a single character caused the change. – rmaddy Jan 07 '19 at 05:46
  • Yes this is just to answer OP why that "unexpected" situation happens and how to fix it. Of course OP should choose a completely different solution. – Ricky Mo Jan 07 '19 at 06:24
  • So, textFieldDidChange does not trigger itself when a change is made programmatically, but textViewDidChange does? Interesting. .. – Greg Jan 10 '19 at 17:51
  • Good point to everyone who mentioned it, what would be a good way to handle it for cut and paste? – Greg Jan 10 '19 at 17:53
  • @Ricky Mo, this doesn't work. Decimals do not show up at all. Also, can you refer me to a good place to learn how to use Regex? – Greg Jan 12 '19 at 19:37
  • Sorry for a miss. This one `^(?:[0-9]+(?:\.$|\.[0-9]+)?(?:\s+|\s*$))+$` should works. [https://regexr.com/](https://regexr.com/) is a good site to learn Regex. – Ricky Mo Jan 13 '19 at 07:53