8

So i want to validate the user ip during typing. In the VC i did the following :

extension NetworkSettingsViewController: UITextFieldDelegate {
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    self.staticMask.resignFirstResponder()
    self.staticGateway.resignFirstResponder()
    self.staticIp.resignFirstResponder()
    self.staticDns.resignFirstResponder()
    return true
}

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

    var isValidate: Bool
    //verify deletion not happening 
    if !(range.length == 1) {
        if validatorManager.verifyTarget(test: string) {

            isValidate = true
        } else {
            isValidate = false
        }
    } else {
        isValidate = true
    }
    return isValidate
}

}

This is the validation class :

 class ValidatorManager: NSObject {

   func verifyTarget(test: String) -> Bool {
    //        let validIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
    let validIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){0,3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])?$"
    let ipTest = NSPredicate(format:"SELF MATCHES %@", validIpAddressRegex)
    print(ipTest.evaluate(with:test))
    return ipTest.evaluate(with:test)
  }
}

i have tried the 2 regex but nothing. i want to check char by char and then all the 3 before the dot() for all the octets.

ironRoei
  • 2,049
  • 24
  • 45

4 Answers4

10

Here are two functions: the first one checks what the user is typing is valid (unfinished IP), and the second one checks the whole thing:

func verifyWhileTyping(test: String) -> Bool {
    let pattern_1 = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){0,3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])?$"
    let regexText_1 = NSPredicate(format: "SELF MATCHES %@", pattern_1)
    let result_1 = regexText_1.evaluate(with: test)
    return result_1
}

func verifyWholeIP(test: String) -> Bool {
    let pattern_2 = "(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})"
    let regexText_2 = NSPredicate(format: "SELF MATCHES %@", pattern_2)
    let result_2 = regexText_2.evaluate(with: test)
    return result_2
}

Use verifyWhileTyping(test:)in textField(_ textField: , shouldChangeCharactersIn range:, replacementString string:) To check while typing. When the user is finished and clicks a button or hits the Enter key call verifyWholeIP(test:):

//While typing
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if let text = textField.text {
        verifyWhileTyping(test: text + string)
    }
    //...
}

//When Enter is tapped
func textFieldShouldReturn(_ textField: UITextField) -> Bool {   
    textField.resignFirstResponder()
    if let text = textField.text {
        verifyWholeIP(test: text)
    }
    //...
    return true
}

pattern_1 checks as the user is typing if it is the beginning of correct IP:

regexText_1.evaluate(with: "0")        //true
regexText_1.evaluate(with: "255")      //true
regexText_1.evaluate(with: "256")      //false
regexText_1.evaluate(with: "10.10.")   //true
regexText_1.evaluate(with: "1.2..")    //false
regexText_1.evaluate(with: "1.2.3.4")  //true
regexText_1.evaluate(with: "1.2.3.4.") //false

As to pattern_2, it evaluates a whole IPv4:

"(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})"

Here are more test cases:

regexText_2.evaluate(with: "0.0.0.0")    //true
regexText_2.evaluate(with: "1.1.1.256")  //false
regexText_2.evaluate(with: "-1.0.1.2")   //false
regexText_2.evaluate(with: "12.34.56")   //false
regexText_2.evaluate(with: "I.am.an.IP") //false

For IPv6 this the regex to use: "[0-9A-Fa-f]{1,4}" in pattern_2.

ielyamani
  • 17,807
  • 10
  • 55
  • 90
  • any suggestions how to check char by char? – ironRoei Sep 03 '18 at 14:12
  • @ironRoei I am playing with it in a playground, I'll put up an updated answer shortly – ielyamani Sep 03 '18 at 14:13
  • @ironRoei I've updated it, I hope it's what you're looking for – ielyamani Sep 03 '18 at 14:19
  • Hi , i have checked the first pattern and it always true, for example when i type "1" it is true(as should be) but also when i type 5. – ironRoei Sep 06 '18 at 07:39
  • @ironRoei isn't `5` still acceptable since the full octet should be included in `0...255` ? – ielyamani Sep 06 '18 at 09:23
  • i dont think so. if i start the first octet with 5 it mins that the number would be 5xx and that is not valid, make sense? – ironRoei Sep 06 '18 at 10:05
  • You mean the user has to type three digits? and something like 50.0.0.1 isn't possible? – ielyamani Sep 06 '18 at 10:06
  • my bad, you right. what will give me false during typing? – ironRoei Sep 06 '18 at 10:08
  • @ironRoei I've updated my answer. If there is an error during typing then `verifyWhileTyping(test:)` would. And it is the one to be used in `textField(_ textField: , shouldChangeCharactersIn range:, replacementString string:)`. Call `verifyWholeIP(test:)` when the user clicks a button or Enter. – ielyamani Sep 06 '18 at 10:11
  • hi there, so i have tried "verifyWhileTyping" and when i am typing a fourth numbers it brings true – ironRoei Sep 10 '18 at 13:08
  • in addition tried to type 55. but it returns false when the dot typed – ironRoei Sep 10 '18 at 13:12
  • func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { var isValidate: Bool // verify deletion not happening if !(range.length == 1) { if validatorManager.verifyWhileTyping(test: string) { isValidate = true } else { isValidate = false } } else { isValidate = true } return isValidate } } – ironRoei Sep 10 '18 at 13:30
  • "*typing a fourth numbers*" Do you mean something like this `verifyWhileTyping(test: "1111")` gives me `false`. and `verifyWhileTyping(test: "55.")`give me `true` which is expecetd too. – ielyamani Sep 10 '18 at 16:03
  • @ironRoei I've updated `textField(textField: shouldChangeCharactersIn range:)`It should be working now. – ielyamani Sep 10 '18 at 16:11
  • So while doing verifyWhileTyping(test: "111") it all seems fine but when i type the extra char "1" and on the textfield i see 1111 i get true. i think you check the whole octet and not the char + octets. make sense? – ironRoei Sep 12 '18 at 07:19
  • @ironRoei I get false for "1111". Could you try this in a playground please: `func verifyWhileTyping(test: String) -> Bool { let pattern_1 = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){0,3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])?$" let regexText_1 = NSPredicate(format: "SELF MATCHES %@", pattern_1) let result_1 = regexText_1.evaluate(with: test) return result_1 } ; verifyWhileTyping(test: "111"); verifyWhileTyping(test: "1111")` – ielyamani Sep 12 '18 at 07:28
  • can we go private ? :) – ironRoei Sep 12 '18 at 07:39
  • Sure, let's go to the chat – ielyamani Sep 12 '18 at 07:40
  • how can we do it ?:) – ironRoei Sep 12 '18 at 07:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179882/discussion-between-carpsen90-and-ironroei). – ielyamani Sep 12 '18 at 07:49
4

You can use a regex, You can also separate the IP address string by dots and check to see if each part is an integer in the range 0 to 255:

func isValidIP(s: String) -> Bool {
    let parts = s.componentsSeparatedByString(".")
    let nums = parts.flatMap { Int($0) }
    return parts.count == 4 && nums.count == 4 && nums.filter { $0 >= 0 && $0 < 256}.count == 4
}

(Assuming you're only checking IPv4 strings.)

You can also do it pretty nicely with a regex.

import UIKit

infix operator =~ {}

func =~ (left: String, right: String) -> Bool {
    do {
        let regex = try NSRegularExpression(pattern: right, options: [])
        return regex.numberOfMatchesInString(left, options: [], range: NSMakeRange(0, (left as NSString).length)) > 0
    } catch {
        return false
    }
}

func isValidIP(s: String) -> Bool {
    let regex = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$"
    return s =~ regex
}

:The problem is that it will match strings like "192.168.256.1", which is not a valid IP address. The regex for checking only valid IPs is actually fairly detailed and non-trivial.

Hope it helps

Chetu Test
  • 113
  • 1
  • 5
  • thanks! i think you didnt understand me. my problem is not with the hole ip , its with the validation during the typing. for example if i entered 1 it is valid but if i entered 5 it is not and so on – ironRoei Sep 03 '18 at 13:48
1

The following allows you to perform an action with each character added/removed from your textField. You will have to adjust the action inside the method. I currently use this method in a production app

textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: UIControlEvents.editingChanged)

I also faced the issue you are having with the shouldChangeCharactersIn method being a character too slow. After reading many sources online I came to the conclusion this method should really only be used with simple logic (Just my opinion)

Once you've added the line of code above, you'll need to implement the UITextFieldDelegate as follows:

//MARK: UITextFieldDelegate
func textFieldDidBeginEditing(_ textField: UITextField) {
    //Do something here
}
Brandon Stillitano
  • 1,304
  • 8
  • 27
  • 1
    thanks for the answer! i think the problem is more in the regex or logic, i dont see a difference in my project between shouldChangeCharactersIn and textFieldDidBeginEditing – ironRoei Sep 03 '18 at 13:44
0

A non-regex alternative that covers both full/partial validation, which can be used UITextFieldDelegate:

class InternetProtocolAddressValidation {

    enum Result { case fail, fullIP4, partialIP4 }

    class func validate(string: String) -> Result {

        let components = string.split(separator: ".")
        let validString = components.compactMap({ UInt8($0) }).map({ String($0) }).joined(separator: ".")

        if components.count == 4 && string == validString {
            return .fullIP4
        } else string.isEmpty || (1...3 ~= components.count && (string == validString || string == validString + ".")) {
            return .partialIP4
        } else {
            return .fail
        }
    }
}
Jens Schwarzer
  • 2,840
  • 1
  • 22
  • 35