Check if a UITextField is characters/numbers only - ios

I have this block of code....
let characterset = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-")
if itemDescription.text!.rangeOfCharacter(from: characterset.inverted) != nil {
print("string contains special characters")
let message = MDCSnackbarMessage()
message.text = "Numbers and letters only."
MDCSnackbarManager.show(message)
progressView.isHidden = true
buttonSearchOutlet.isHidden = false
}
No matter what I type in, I get the snackbar to show. I expect these to be false
Hello
hello
hello1
hello-
I expect this to be true
Hello!
hello's
hello1#
hello$&##
What did I do wrong?

You can simply check if all characters satisfy a condition and check if your string with the valid characters contais those characters.
extension StringProtocol {
var isLetterDigitOrHyphen: Bool {
allSatisfy("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-".contains)
}
}
Usage:
let str = "Hello"
if str.isLetterDigitOrHyphen {
print("string is ok")
} else {
print("string contains special characters")
}
"Hello".isLetterDigitOrHyphen // true
"hello".isLetterDigitOrHyphen // true
"hello1".isLetterDigitOrHyphen // true
"hello-".isLetterDigitOrHyphen // true
"Hello!".isLetterDigitOrHyphen // false
"hello's".isLetterDigitOrHyphen // false
"hello1#".isLetterDigitOrHyphen // false
"hello$&##".isLetterDigitOrHyphen // false
If you would like to use regular expressions:
extension StringProtocol {
var isLetterDigitOrHyphen: Bool {
range(of:"^[a-zA-Z0-9-]{1,}$", options: .regularExpression) != nil
}
}
Or using a character set:
extension StringProtocol {
var isLetterDigitOrHyphen: Bool {
unicodeScalars.allSatisfy(CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-").contains)
}
}
Of course you can simply negate the result if you would like to:
extension String {
static let validCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
}
extension StringProtocol {
var isNotLetterDigitOrHyphen: Bool {
!unicodeScalars.allSatisfy(CharacterSet(charactersIn: .validCharacters).contains)
}
}

Related

How to make sure that a string contains a whole word not just part of it

I need some code to make sure that if a whole word exists in a return formatted text file it is accepted and that, if only part of it is present, it is not considered.
If I type lau in the TextField it is accepted and I would rather the answer was false until a whole word is matched
Here is the file limited.txt I use in my project. Each word is on a separate line:
appetitive
appetitiveness
appetitost
appetize
appetized
appetizement
appetizer
appetizers
appetizing
appetizingly
appinite
appius
appl
applanate
applanation
applaud
applaudable
applaudably
applauded
applauder
applauders
applauding
applaudingly
applauds
applause
applauses
applausive
applausively
apple
appleberry
appleblossom
applecart
appled
appledrane
appledrone
applegrower
applejack
applejohn
applemonger
Thanks for your help
import SwiftUI
struct ContentView: View{
#ObservedObject var textFileStringContent: TexFileReader
#State private var text = ""
var body: some View{
VStack {
TextField("please type the word to check", text: $text)
// so that it does not matter if user capitalises a word
if textFileStringContent.data.contains(self.text.lowercased()) {
Text("part of it exists")
// I tried to code it in here but to no avail
// if it is a whole word {
// Text("congratulations it does exist")
// }
} else if !text.isEmpty {
Text("sorry no such word")
}
}.padding().font(.headline)
.navigationBarTitle("Word checker")
}
}
class TexFileReader: ObservableObject {
#Published var data: String = ""
init() { self.load(file: "limited") }
func load(file: String) {
if let filepath = Bundle.main.path(forResource: file, ofType: "txt") {
do {
let contents = try String(contentsOfFile: filepath)
DispatchQueue.main.async {
self.data = contents
print(self.data.contains("lau"))
// this prints true even if lau is not a whole word
// applaud
// applaudable
// applaudably
// applauded
// applauder
// applauders
// applauding
// applaudingly
// applauds
// applause
// applauses
// applausive
// applausively
// but present in each of these
// I need to make sure that the match is a whole word not just part of one
}
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("File not found")
}
}
}
A possible way is to search with Regular Expression and the word boundary specifier \\b
if textFileStringContent.data.range(of: "\\b\(self.text)\\b", options: [.caseInsensitive, .regularExpression]) != nil {
You may check if it ends with a newline separator in your text file:
let textWithNewline = self.text.lowercased() + "\n"
if textFileStringContent.data.contains(textWithNewline) {
// it is a whole word
}
Foundation contains a language analysis engine NSLinguisticTagger which can do many things including finding specific words with locale sensitivity.
A simple implementation of what you're trying to do is:
//extension via https://stackoverflow.com/questions/15062458/shortcut-to-generate-an-nsrange-for-entire-length-of-nsstring/56391610#56391610
extension String {
func range(from nsRange: NSRange) -> Range<String.Index>? {
return Range(nsRange, in: self)
}
}
var tagger = NSLinguisticTagger(tagSchemes: [NSLinguisticTagScheme.tokenType], options: 0)
let baddata = """
applaud
applaudable
applaudably
applauded
applauder
applauders catlau
applauding
"""
let gooddata = """
applaud
applaudable
applaudably
applauded
applauder
applauders lau catlau
applauding
"""
var foundLau = false
tagger.string = baddata
tagger.enumerateTags(in: NSRange(location: 0, length: baddata.count), scheme: .tokenType, options: [.omitWhitespace]) { tag, tokenRange, _, _ in
if tag != nil, let range = baddata.range(from: tokenRange) {
let fragment = baddata[range]
if fragment.lowercased() == "lau" {
foundLau = true
}
}
}
print("found \"lau\" in baddata =", foundLau ? "true":"false")
tagger.string = gooddata
tagger.enumerateTags(in: NSRange(location: 0, length: gooddata.count), scheme: .tokenType, options: [.omitWhitespace]) { tag, tokenRange, _, _ in
if tag != nil, let range = gooddata.range(from: tokenRange) {
let fragment = gooddata[range]
if fragment.lowercased() == "lau" {
foundLau = true
}
}
}
print("found \"lau\" in gooddata =", foundLau ? "true":"false")
enumerateTags returns an NSRange which can be converted to Range for general Swift use.

Swift iOS Mask a string "Hello" to "Hxxxo"

extension String {
var masked: String {
// some logic which I have to write to mask string.
// I tried following and just shows x 🤦‍♂️
// replacingOccurrences(
// of: ".(.+).",
// with: "x",
// options: .regularExpression,
// range: nil
//)
}
}
let helloWorld = "Hello World"
print("Masked string is - \(helloWorld.masked)")
Expected output is - "Hxxxxxxxxxd"
There is a Regular Expression way with lookaround
extension String {
var masked: String {
replacingOccurrences(
of: "(?!^).(?!$)", // RegEx
with: "x", // Replacement
options: .regularExpression // Option to set RegEx
)
}
}
You can enumerate the string and apply map transform to get the expected output:
extension String {
var masked: String {
self.enumerated().map({ (index, ch) in
if index == 0
|| index == self.count - 1 {
return String(ch)
}
return "x"
}).joined()
}
}
let str = "hello"
print("after masking \(str.masked)") // Output - hxxxo
The map transform will return an array, so use joined() to convert the array back to String. Also, note that you have to typecast ch to String as String(ch) because the type of ch is 'String.Element' (aka 'Character').
extension Sequence {
func replacingEachInteriorElement(with replacement: Element) -> [Element] {
let prefix = dropLast()
return
prefix.prefix(1)
+ prefix.dropFirst().map { _ in replacement }
+ suffix(1)
}
}
extension String {
var masked: Self {
.init( replacingEachInteriorElement(with: "x") )
}
}
"Hello World".masked == "Hxxxxxxxxxd" // true
"H🦾👄🐺🥻🐸🦈🏄‍♂️🍯🪐d".masked == "Hello World".masked // true
"🥮".masked // 🥮
"🥶😎".masked // 🥶😎
[].replacingEachInteriorElement(with: 500) // []
My solution without using Regular Expression:
extension String {
var masked: String {
if self.count < 2 { return self }
var output = self
let range = self.index(after: self.startIndex)..<self.index(before: endIndex)
let replacement = String.init(repeating: "x", count: output.count - 2)
output.replaceSubrange(range, with: replacement)
return output
}
}
So far, I've found following solution.
extension String {
var masked: String {
var newString = ""
for index in 0..<count {
if index != 0 && index != count-1 {
newString.append(contentsOf: "x")
} else {
let array = Array(self)
let char = array[index]
let string = String(char)
newString.append(string)
}
}
return newString
}
}
If you want to leave first and last letters you can use this ->
public extension String {
var masked: String {
return prefix(1) + String(repeating: "x", count: Swift.max(0, count-2)) + suffix(1)
}
}
USAGE
let hello = "Hello"
hello.masked
// Hxxxo
OR
you can pass unmasked character count ->
public extension String {
func masked(with unmaskedCount: Int) -> String {
let unmaskedPrefix = unmaskedCount/2
return prefix(unmaskedPrefix) + String(repeating: "x", count: Swift.max(0, count-unmaskedPrefix)) + suffix(unmaskedPrefix)
}
}
USAGE
let hello = "Hello"
hello.masked(with: 2)
// Hxxxo
let number = "5555555555"
number.masked(with: 4)
// 55xxxxxx55

How to read a specific format ( XXX.X ) from UItextfield in swift?

I want to read a text from the text field in swift with XXX.X format.Suppose By XXX.X format I mean:
123.3 is valid
48.2 is valid
1.4 is valid
4 is valid
4.3.4 is invalid
5555.5 is invalid
123.34 is invalid
In other words, the text on the left side of the period must only have at most 3 characters and on the left side, must only have 1 character or no character at all if the user did not input "."
For now, my code works only for restriction of the number of "."
func textField(textField: UITextField,shouldChangeCharactersIn range: NSRange,replacementString string: String) -> Bool
{
let countdots = textField.text.components(separatedBy: ".").count)! - 1
if countdots > 0 && string == "."
{
return false
}
return true
}
func valid(_ s:String) -> Bool {
func roundToPlaces(value:Double, places:Int) -> Double {
let divisor = pow(10.0, Double(places))
return round(value * divisor) / divisor
}
if let num = Double(s) {
if num >= 1000.0 {
return false
}
if roundToPlaces(value:num, places:1) != num {
return false
}
return true
} else {
return false
}
}
Test on your input:
let test = [
"123.3",
"48.2",
"1.4",
"4",
"4.3.4",
"5555.5",
"123.34"
]
for s in test {
print(valid(s))
}
Result:
true
true
true
true
false
false
false
So that is your input and your desired output.
Try something like this (Regular Expression Might be wrong you need to fix it yourself)
-(BOOL)isValidValue:(NSString *)checkString{
NSString *stricterFilterString = #"^[0-9]{1,3}[.][0-9]{1}";
NSPredicate *valueTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", stricterFilterString];
return [valueTest evaluateWithObject:checkString];
}
Test:
NSLog(#"%#", [self isValidValue:#"123.3"] ? #"YES" : #"NO");
[Update]
for Swift 3:
func valueCheck(_ checkString: String) -> Bool {
var result = false
do {
let regex = try RegularExpression(pattern: "^[0-9]{1,3}[.][0-9]{1}", options: [])
let results = regex.matches(in: checkString, options: [], range: NSMakeRange(0, checkString.characters.count))
if results.count > 0 {
return true
} else {
return false
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
}
return false
}

How can I check for empty textfields and email, password regex before calling a function?

I'm trying to check for empty textfields and email and password regex before calling my chkInternet function, but can't figure it out, tried nested if statement, and making individual if statements with an else calling the function but that didn't work either, this is what I got so far but I'm stuck:
#IBAction func RegisterBTN(_ sender: Any) {
let userEmail = userEmailTxtField.text!
let userPhone = phoneNumberTxtField.text!
let password = passwordTxtField.text!
let passConfirm = passConfirmTxtField.text!
let emailValid = isValidEmailAddress(emailAddressString: userEmail)
let passValid = isPasswordValid(passWordString: password)
if userEmail.isEmpty{
emailErrorImg.isHidden = false
}
if userPhone.isEmpty{
phoneErrorImg.isHidden = false
}
if password.isEmpty{
passwordErrorImg.isHidden = false
}
if passConfirm.isEmpty{
passConfirmErrorImg.isHidden = false
}
if !emailValid {
emailErrorImg.isHidden = false
}
if !passValid {
passwordErrorImg.isHidden = false
}
ChkInternet()
}
Others have pointed out decent solutions, but there's a simple way to remove a lot of code repetition here:
let fieldImagePairs = [
(userEmailTxtField, emailErrorImg),
(phoneNumberTxtField, phoneErrorImg),
(passwordTxtField, passwordErrorImg),
(passConfirmTxtField, passConfirmErrorImg)
]
for (textField, errorImage) in fieldImagePairs {
guard textField.text?.isEmpty == false else {
errorImage.isHidden = false
return
}
}
guard emailValid else {
emailErrorImg.isHidden = false
return
}
guard passValid else {
passwordErrorImg.isHidden = false
return
}
ChkInternet()
If all of these codes are inside any function with no return type, simply adding return will suffice
if userEmail.isEmpty {
emailErrorImg.isHidden = false
return
}
if userPhone.isEmpty {
phoneErrorImg.isHidden = false
return
}
if password.isEmpty {
passwordErrorImg.isHidden = false
return
}
if passConfirm.isEmpty {
passConfirmErrorImg.isHidden = false
return
}
if !emailValid {
emailErrorImg.isHidden = false
return
}
if !passValid {
passwordErrorImg.isHidden = false
return
}
ChkInternet()
}
What return does is that it ends the execution of code inside a function block, meaning if return is called any and all codes below it will not be called anymore, therefore not calling your ChkInternet() function
For email validation simply add an extension to String
extension String
var isValidEmail: Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluateWithObject(self)
}
}
// use case
if !someTextfield.text.isValidEmail {
return // do nothing cause not valid
}
// working codes here
// or
let someString: String = "asdasdawdaw"
if !someString.text.isVaildEmail {
return // do nothing cause not valid
}
// working codes here
// or
let someString: String = "asdasdawdaw"
guard someString.text.isValidEmail else {
return // do nothing cause not valid
}
// working codes here
i am using third party library to remove this headache. try this SwiftValidator

Error handling: Cannot convert type in Swift 2

How do I catch an error if a function argument is not of the correct type?
Function:
func returnBool(someBool: Bool) -> Bool {
return someBool
}
Given:
returnBool("not a Bool")
Result:
"Cannot convert value of type..."
What I want to see:
false, nil or the like + no error
Compiler won't allow you to do like this way. If you want to check some param, which can be any object, then use AnyObject and check it with guard statement:
func returnBool(someBool: AnyObject) -> Bool {
guard someBool is Bool else { return false }
return true
}
let result1 = returnBool("test") // false
let result2 = returnBool(false) // true
let result3 = returnBool(1) // true
or
func returnBool(someBool: AnyObject) -> Bool? {
guard someBool is Bool else { return nil }
return someBool as? Bool
}
let result1 = returnBool("test") // nil
let result2 = returnBool(false) // false
let result3 = returnBool(1) // true

Resources