Bronze Challenge messy solution

Couldn’t fine another way of doing this, simpler solution appreciated!

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool {
    let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
    let replacementTextHasDecimalSeparator = string.range(of: ".")
    let ischaracterset = string.rangeOfCharacter(from: NSCharacterSet.letters)
    
    if ischaracterset != nil{
        return false
    }
    else {
        if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil
        {
            return false
        }
        else
        {
            return true
        }
    }
}

Hi @jgegrm here is my solution if it helps.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
    let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
    let replacementTextHasDecimalSeparator = string.range(of: ".")
    let isAlphaneumeric = string.rangeOfCharacter(from: CharacterSet.letters)
    
    if ((existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil) || isAlphaneumeric != nil) {
        return false
    } else {
        return true
    }
}

I wrote an explanation here that might help others understand the code.

1 Like

This solution is quite verbose, but I believe it’s really readable.

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool {
    
    let dotAndNumbersCharacterSet = CharacterSet.init(charactersIn: "0123456789.")
    let replacementTextCharacterSet = CharacterSet.init(charactersIn: string)
    
    let replacementTextIsDotOrNumber = replacementTextCharacterSet.isSubset(of: dotAndNumbersCharacterSet)
    let replacementTextIsControlCharacter = replacementTextCharacterSet.isSubset(of: CharacterSet.controlCharacters)
    let existingTextHasDecimalSeparator = {
        return textField.text?.range(of: ".") != nil
    }()
    let replacementTextHasDecimalsSeparator = {
        return string.range(of: ".") != nil
    }()
    
    
    if replacementTextIsDotOrNumber ||
        replacementTextIsControlCharacter {
        
        if existingTextHasDecimalSeparator &&
            replacementTextHasDecimalsSeparator {
            return false
        }
        
        return true
    }
    
    return false
}

I found an other simple solution:

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

        let characterAuthorized = CharacterSet.decimalDigits
        let arrayOfCharacter = ".,-".components(separatedBy: ",")
        let characterAdded = CharacterSet.init(charactersIn: string)
        
        if characterAuthorized.isSuperset(of: characterAdded) { return true }    
        else if arrayOfCharacter.contains(string) { return true }
        else { return false }
    }

inspired by other solutions, here is mine

    let invalidCharacterSet = NSCharacterSet(charactersIn: "0123456789.-").inverted
    let replacementTextHasInvalidCharacter = string.rangeOfCharacter(from: invalidCharacterSet)
    
    if string == "-" {
        if let existingText = textField.text, !existingText.isEmpty {
            return false
        }
    }
    
    let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
    let replacementTextHasDecimalSeparator = string.range(of: ".")
    
    if replacementTextHasInvalidCharacter == nil{
        if existingTextHasDecimalSeparator != nil, replacementTextHasDecimalSeparator != nil {
            return false
        } else {
            return true
        }
    } else {
        return false
    }

Hi,

I just joined the forum but came up independently with a similar solution, which however causes problems. In the playground I wrote this and it works:

func tellDigits() -> Bool {
let characterSet = NSCharacterSet.letters
let phrase = “678”

let stringOfCharacters = phrase.rangeOfCharacter(from: characterSet) // gives nil without error although it is not optional
// range will be nil if no letters is found
if stringOfCharacters == nil {
    return true
} else {
    return false
}

}
tellDigits() // gives true as it should

However, if I incorporate that into the application, I get errors.

let characterSet = NSCharacterSet.letters

let stringOfCharacters = textField.text.rangeOfCharacter(from: characterSet)
if stringOfCharacters != nil {
return false
} else {
return true
} // error: value of optional type string not unwrapped.
if stringOfCharacters != nil {
return false
} else {
return true
}

if instead I write
let stringOfCharacters = string.rangeOfCharacter(from: characterSet)
I get an attention mark that this will never be executed, most probably because of the same reason. Also we want the text in the textfield to be prevented from having letters first. So I don’t understand, how to unwrap “stringOfCharacters”. I tried everything but without success. There must be a different solution.