Filtering input characters for a numeric text field using logical operators I did my code for "Bronze Challenge: Disallow Alphabetic characters" exercise in "iOS programming" book by Big Nerd Ranch (p.85). When I did the exercise I also changed code of func textField which was given in the book.

The exercise was to disallow alphabetic characters when user either use Bluetooth keyboard or by passing copied text from clipboard. Also the func does not allow to enter several decimal separators:

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -&gt; Bool {

    let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
    let replacementTextHasDecimalSeparator = string.range(of: ".")

    //check letters in replacement text:
    let letters = NSCharacterSet.letters
    let isRangeInReplacementString = string.rangeOfCharacter(from: letters)

    //check if there are double decimal separators (operator AND)
    //OR if there is a letter
    //then returnValueBool will be TRUE
    let returnValueBool = ((existingTextHasDecimalSeparator != nil)
        &amp;&amp; (replacementTextHasDecimalSeparator != nil))
        || (isRangeInReplacementString != nil)


    //So returnValueBool is true if there is double digital separator or there is a letter. Function returns False in this case
     return !returnValueBool

    }


My code works well in simulator but I would like somebody experienced in Swift to have a look at my code and to comment on using logical operators.
Let's start with the assignment

let existingTextHasDecimalSeparator = textField.text?.range(of: ".")


The variable name suggests a boolean condition, but the value
is an optional range. It would be clearer to make it a boolean value:

let existingTextHasDecimalSeparator = textField.text?.range(of: ".") != nil


and similarly 

let replacementTextHasDecimalSeparator = string.range(of: ".") != nil


The variable name isRangeInReplacementString is meaningless, this would
become

let existingTextHasLetters = string.rangeOfCharacter(from: .letters) != nil


and here .letters is inferred as CharacterSet.letters, so we don't
need an extra variable for that.

The variable name returnValueBool is also meaningless, that might
become

let isInvalidChange = (existingTextHasDecimalSeparator &amp;&amp; replacementTextHasDecimalSeparator)
    || existingTextHasLetters
return !isInvalidChange


Now some simplifications: if the existing text has no decimal separator
then there is no need to check the replacement text for a separator.
And if you already detected a duplicate separator then you don't have
to check for invalid characters (letters). In other words: short-circuit:

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -&gt; Bool {

    if textField.text?.range(of: ".") != nil &amp;&amp; string.range(of: ".") != nil {
        return false
    }
    if string.rangeOfCharacter(from: .letters) != nil {
        return false
    }
    return true
}


But there are still two problems:


It is not possible to replace text containing the decimal separator
with new text also containing the decimal separator with copy/paste,
e.g. replace 1.2 by 3.4.
The decimal separator is locale dependent, it can be a period,
a comma (e.g. in Germany) and perhaps also other characters.


The first problem can be solved by computing the new string after
the replacement first, and then check if that is valid (only digits
and at most one decimal separator).

The second problem can be solved by using a NumberFormatter to check
if the text is a valid decimal number.

That would lead to the following implementation (which is essentially
a translation of the code in Big Nerd Ranch Bronze Challenge: Disallow Alphabetic Characters
to the current Swift 4):

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -&gt; Bool {

    guard let text = textField.text else { return false }
    // Compute new text after replacement:
    let newText = text.replacingCharacters(in: Range(range, in: text)!, with: string)
    // Check for empty string:
    if newText.isEmpty || newText == "-" {
        return true
    }
    // Check for valid decimal number:
    let formatter = NumberFormatter()
    formatter.numberStyle = .decimal
    return formatter.number(from: newText) != nil
}