1

I am trying to do a text field for credit card expiry date and using following code.

    if range.length > 0 {
        return true
    }
    if string == "" {
        return false
    }
    if range.location > 6 {
        return false
    }
    var originalText = textField.text
    let replacementText = string.replacingOccurrences(of: " ", with: "")

    if !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: replacementText)) {
        return false
    }

    if range.location == 4 {
        originalText?.append("/")
        textField.text = originalText
    }
    return true

However, using that code user can write 5145/52 which is not normal for expiry date. How can I limit year between i.e. 2000 to 2018 and month values 01 to 12?

atalayasa
  • 3,310
  • 25
  • 42
  • Post the whole method from which this code is extracted. (is that taken from a `textField(_:shouldChangeCharactersIn:replacementString:)` method?) – Duncan C Dec 22 '17 at 14:46
  • In my viewDidLoad method I am calling following function expiryField.addTarget(self, action: #selector(textField(_:shouldChangeCharactersIn:replacementString:)), for: .editingDidBegin) – atalayasa Dec 22 '17 at 14:51
  • 2
    Btw why would you limit the year to 2018? the year range should be a range from the current year until something like 50 years from now. – Leo Dabus Dec 22 '17 at 15:09
  • 1
    @AtalayAsa Is there a reason you're lumping year and month into one textField? – Adrian Dec 22 '17 at 15:10
  • 3
    I wont write an answer cause technically this is not, but I strongly suggest you use pickers for this kind of inputs, in that way you control everything and automatically have less to do – AntonijoDev Dec 22 '17 at 15:21
  • I think this Regex string should work for what you're trying to do. `enum RegexString: String { case validDate = "([2][0][01]{1,2}[0-8]/[1-9][12])" }`. Have a peek at this answer: https://stackoverflow.com/a/27880748/4475605. For the regex var, use `RegexString.validDate.rawValue`. There's a website called regex101.com you can play with regex strings or an MacOS App Store app called Patterns for when you're not online. – Adrian Dec 22 '17 at 15:30
  • You said "In my viewDidLoad method I am calling following function expiryField.addTarget(self, action: #selector(textField(_:shouldChangeCharactersIn:replacementSt‌​ring:)), for: .editingDidBegin)". That is very, very wrong and does not make any sense whatsoever. The method `textField(_:shouldChangeCharactersIn:replacementString:)` is a `UITextFieldDelegate` method, not an IBAction method. As long as you set up your view controller as the delegate of the text field, it will call that method if you implement it. – Duncan C Dec 22 '17 at 18:44
  • @LeoDabus 2018 is just an example for my question. It will be like you said starting from 2000 to 2050. – atalayasa Dec 23 '17 at 18:56
  • @Adrian Because this text field will be used for both expiry date and period of specific time like starting from 2018/12 to 2020/09. – atalayasa Dec 23 '17 at 18:58
  • @DuncanC Actually it was my mistelling. There are 20 text fields in my view controller and I need to have only one text field that using that function it is why I have added in there. I am not so good at swift development could you please tell me where should I put that target? Btw I set up delegate of text field to my view controller. Thanks. – atalayasa Dec 23 '17 at 19:04
  • I also thought using picker view but my customer did not accept it that is why I choosed that way. Thank you. – atalayasa Dec 23 '17 at 19:06
  • @AtalayAsa https://stackoverflow.com/a/43016782/2303865 – Leo Dabus Dec 23 '17 at 19:33
  • @AtalayAsa Finally had a chance to give you a hand and the question got closed. Here's a repo for you. github.com/AdrianBinDC/TextFieldValidationQuestion – Adrian Dec 24 '17 at 03:28
  • @Adrian Your code is working fine thank you for this. However, when I started to delete it stucked on the middle of text field. (2016/). – atalayasa Dec 25 '17 at 06:34

1 Answers1

0

Usually, the formatting of the entered text and its validation are two separate issues. It's a really good place to use NSRegularExpression. Here you have an example of the date validation with a year in the range of 2000-2099 and a month between 01-12.

func validate(string: String) -> Bool {
    let regex = try! NSRegularExpression(pattern: "^(20)\\d\\d[/](0[1-9]|1[012])$")

    return regex.firstMatch(in: string, options: [], range: NSMakeRange(0, string.characters.count)) != nil
}

validate(string: "2012/02") // true
validate(string: "2012/2")  // false
validate(string: "1912/12") // false
validate(string: "2012/112") // false

Update:

In your case it would look like this:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard let text = textField.text else { return true }
    let newLength = text.count + string.count - range.length
    let characterSet = CharacterSet(charactersIn: string)

    if text.characters.count == 4, !string.characters.isEmpty {
        textField.text = text + "/"
    }

    return CharacterSet.decimalDigits.isSuperset(of: characterSet) && newLength <= 7
}

func textFieldDidEndEditing(_ textField: UITextField) {
    guard let text = textField.text else { return }
    let isUserInputValid = validate(string: text)

    if !isUserInputValid {
        //TODO: Notify user that expiration date is invalid
    }
}
Tomasz Pe
  • 696
  • 7
  • 19
  • So are you saying in my (_:shouldChangeCharactersIn:replacementString:) shall I call that validate function ? – atalayasa Dec 23 '17 at 19:09
  • Check my edit :) Validation can be carried out in various places it depends on your needs. – Tomasz Pe Dec 24 '17 at 15:51
  • I tried it and it is working well thanks. However that textFieldDidEndEditing function is working for all of my textfields and it is causing a problem because I also have price text field that user writes item price. Because of they are in same class two of them are using same textFieldDidEndEditing function how can I separate them from each other? – atalayasa Dec 25 '17 at 13:42
  • You could simply check if the textField argument is equal to the particular textField: `if let text = textField.text, textField == expirationDateTextField { //Validate expiration date }` – Tomasz Pe Dec 26 '17 at 10:13