Regex issue for name field - Swift - swift

I am trying to create a regex which can be used for a name.
static let nameRegex = try! NSRegularExpression(pattern: "^[a-zA-Z][A\\p{L}z'\\s]{0,19}$", options: [])
I am facing 2 issues:
If I type apostrophe, it does not let me type anything further.
I am unable to delete the first character on backspace in textfield because of [a-zA-Z] in the regex.
I am trying to make it such that it should have a limit of 20 characters, should start with an alphabet, and should allow special characters to accept names such as : José, names with apostrophes too.
I am checking the regex like this:
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let textFieldText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let filtered: [NSTextCheckingResult] = Constant.Regex.nameRegex.matches(in: textFieldText, options: [], range: NSMakeRange(0, textFieldText.count))
return filtered.count == 1
}
}
Any help will be appreciated!

You may use
"^(?!\\P{L})[\\p{L}'\\s]{0,20}$"
The pattern matches a string that fully matches the following patterns:
^ - start of string
(?!\\P{L}) - a negative lookahead that fails the match if the next char is not a non-letter char (it requires a letter to the right of the current location or end of string)
[\\p{L}'\\s]{0,20} - 0 to 20 letters, ' or whitespaces
$ - end of string.

Related

Split String or Substring with Regex pattern in Swift

First let me point out... I want to split a String or Substring with any character that is not an alphabet, a number, # or #. That means, I want to split with whitespaces(spaces & line breaks) and special characters or symbols excluding # and #
In Android Java, I am able to achieve this with:
String[] textArr = text.split("[^\\w_##]");
Now, I want to do the same in Swift. I added an extension to String and Substring classes
extension String {}
extension Substring {}
In both extensions, I added a method that returns an array of Substring
func splitWithRegex(by regexStr: String) -> [Substring] {
//let string = self (for String extension) | String(self) (for Substring extension)
let regex = try! NSRegularExpression(pattern: regexStr)
let range = NSRange(string.startIndex..., in: string)
return regex.matches(in: string, options: .anchored, range: range)
.map { match -> Substring in
let range = Range(match.range(at: 1), in: string)!
return string[range]
}
}
And when I tried to use it, (Only tested with a Substring, but I also think String will give me the same result)
let textArray = substring.splitWithRegex(by: "[^\\w_##]")
print("substring: \(substring)")
print("textArray: \(textArray)")
This is the out put:
substring: This,is a #random #text written for debugging
textArray: []
Please can Someone help me. I don't know if the problem if from my regex [^\\w_##] or from splitWithRegex method
The main reason why the code doesn't work is range(at: 1) which returns the content of the first captured group, but the pattern does not capture anything.
With just range the regex returns the ranges of the found matches, but I suppose you want the characters between.
To accomplish that you need a dynamic index starting at the first character. In the map closure return the string from the current index to the lowerBound of the found range and set the index to its upperBound. Finally you have to add manually the string from the upperBound of the last match to the end.
The Substring type is a helper type for slicing strings. It should not be used beyond a temporary scope.
extension String {
func splitWithRegex(by regexStr: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: regexStr) else { return [] }
let range = NSRange(startIndex..., in: self)
var index = startIndex
var array = regex.matches(in: self, range: range)
.map { match -> String in
let range = Range(match.range, in: self)!
let result = self[index..<range.lowerBound]
index = range.upperBound
return String(result)
}
array.append(String(self[index...]))
return array
}
}
let text = "This,is a #random #text written for debugging"
let textArray = text.splitWithRegex(by: "[^\\w_##]")
print(textArray) // ["This", "is", "a", "#random", "#text", "written", "for", "debugging"]
However in macOS 13 and iOS 16 there is a new API quite similar to the java API
let text = "This,is a #random #text written for debugging"
let textArray = Array(text.split(separator: /[^\w_##]/))
print(textArray)
The forward slashes indicate a regex literal

Regular expression to find a number ends with special character in Swift

I have an array of strings like:
"Foo", "Foo1", "Foo$", "$Foo", "1Foo", "1$", "20$", "1$Foo", "12$$", etc.
My required format is [Any number without dots][Must end with single $ symbol] (I mean, 1$ and 20$ from the above array)
I have tried like the below way, but it's not working.
func isValidItem(_ item: String) -> Bool {
let pattern = #"^[0-9]$"#
return (item.range(of: pattern, options: .regularExpression) != nil)
}
Can some one help me on this? Also, please share some great links to learn about the regex patterns if you have any.
Thank you
You can use
func isValidItem(_ item: String) -> Bool {
let pattern = #"^[0-9]+\$\z"#
return (item.range(of: pattern, options: .regularExpression) != nil)
}
let arr = ["Foo", "Foo1", "Foo$", "$Foo", "1Foo", "1$", "20$", "1$Foo", "12$$"]
print(arr.filter {isValidItem($0)})
// => ["1$", "20$"]
Here,
^ - matches start of a line
[0-9]+ - one or more ASCII digits (note that Swift regex engine is ICU and \d matches any Unicode digits in this flavor, so [0-9] is safer if you need to only match digits from the 0-9 range)
\$ - a $ char
\z - the very end of string.
See the online regex demo ($ is used instead of \z since the demo is run against a single multiline string, hence the use of the m flag at regex101.com).

Swift UITextfield and CharacterSet cannot delete previous user's input

I'm writing code restricting the user's ability to enter alphabetic texts and repeating decimal into the textField.
In the code below my text field were able to accept only user's numeric input and so does the code I commented out however, the only difference is the user can't delete after he/she type in a number.
Whereas, the user were able to delete after he/she type in the number if I replace the current code with the commented code.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimalSeperator = textField.text?.range(of: ".")
let replacementTextHasDecimalSeperator = string.range(of: ".")
// let replacementTextCharacterSet = CharacterSet(charactersIn: string)
// let replacementTextIsAlphabetic = (replacementTextCharacterSet.isDisjoint(with: CharacterSet.letters) == false)
///This means an element in set A intersect with an element in set B
let replacementTextCharacterSet = CharacterSet(charactersIn: string)
let replacementTextIsAlphabetic = (replacementTextCharacterSet.isSubset(of: CharacterSet.letters) == true)
if (existingTextHasDecimalSeperator != nil && replacementTextHasDecimalSeperator != nil) || replacementTextIsAlphabetic {
return false
} else {
return true
}
The code that was commented out above work as intended however, the current code isn't and I failed to understand why.
Can you please explain to me the reason why I can't delete what I input afterward.
If I use .isSubset = true the logic seems the same to me.
you need to handle backspace in your shouldChangeCharactersIn function
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619599-textfield
string
The replacement string for the specified range. During typing,
this parameter normally contains only the single new character that
was typed, but it may contain more characters if the user is pasting
text. When the user deletes one or more characters, the replacement
string is empty.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (string.isEmpty){
print("Backspace")
}
return true
}

City Name validation Regex Swift 4

I have been trying to implement a regex validation for a given city name.
My regex should match cities names like:
NY
San Francisco
München
København
Saint-Tropez
St. Lucia
So far I've searched the web for such-like regex but I've been having a hard time trying to implement it in swift.
this is the regex I've come up with, it seems to work in other languages but not in swift:
^[a-zA-Z\\u0080-\\u024F\\s\\/\\-\\)\(\`\.\"\']+$
Also as additional info, I'd like to implement it in the UITextField delegate method:
shouldChangeCharactersIn
something like this:
override func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
return validNameRegex.matches(string)
}
using a NSRegularExpression Extension:
extension NSRegularExpression {
convenience init(_ pattern: String) {
do {
try self.init(pattern: pattern)
} catch {
preconditionFailure("Illegal regular expression: \(pattern).")
}
}
func matches(_ string: String) -> Bool {
let range = NSRange(location: 0, length: string.utf16.count)
return firstMatch(in: string, options: [], range: range) != nil
}
}
Hope someone can help me out. Thanks in advance, and happy new year :)
In the end I ended up using this regex:
^[a-zA-Z\u{0080}-\u{024F}\\s\\/\\-\\)\\(\\`\\.\\\"\\']*$
the main problem was scaping the "\" characters and the { } in the unicode

I can't include ' symbol to Regular Expressions

I try to include ' symbol to Regular Expressions
I use this function
func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: text,
range: NSRange(text.startIndex..., in: text))
return results.map {
text.substring(with: Range($0.range, in: text)!)
}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
and this Regular Expressions
let matched = matches(for: "^[‘]|[0-9]|[a-zA-Z]+$", in: string)
when I search I can find numbers and english letters
but not ' symbol
I guess that what you really want is this:
"['0-9a-zA-Z]+"
Note that I have removed the ^ (text start) and $ (text end) characters because then your whole text would have to match.
I have merged the groups because otherwise you would not match the text as a whole word. You would get separate apostrophe and then the word.
I have changed the ‘ character into the proper ' character. The automatic conversion from the simple apostrophe is caused by iOS 11 Smart Punctuation. You can turn it off on an input using:
input.smartQuotesType = .no
See https://developer.apple.com/documentation/uikit/uitextinputtraits/2865931-smartquotestype