Use of unresolved identifier while using extension in swift 3 - swift

I have made a String extension for validating the form in Swift 3 language.
The code is below:
import UIKit
extension String {
// Validating Email ID
func isValidEmail(testStr:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluate(with: testStr)
}
// Validating the User name
func isValidUserName(testStr:String) -> Bool {
let RegEx = "\\A\\w{7,18}\\z"
let Test = NSPredicate(format:"SELF MATCHES %#", RegEx)
return Test.evaluate(with: testStr)
}
// Validating the phone number
var isPhoneNumber: Bool {
do {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.phoneNumber.rawValue)
let matches = detector.matches(in: self, options: [], range: NSMakeRange(0, self.characters.count))
if let res = matches.first {
return res.resultType == .phoneNumber && res.range.location == 0 && res.range.length == self.characters.count
} else {
return false
}
} catch {
return false
}
}
// validating the password
/*
Use the function of Swift 3.0.
1. 8 characters length
2. alphabet
3. special character
regex Syntax Explanation :
(?=.[a-z]) for Character.
(?=.[$#$#!%?&]) for special character.
{8,} for length which you want to prefer.
*/
func isPasswordValid(_ password : String) -> Bool{
let passwordTest = NSPredicate(format: "SELF MATCHES %#", "^(?=.*[a-z])(?=.*[$#$#!%*?&])[A-Za-z\\d$#$#!%*?&]{8,}")
return passwordTest.evaluate(with: password)
}
// validating the password and confirm password are same........................
func isPasswordSame(password: String , confirmPassword : String) -> Bool {
if password == confirmPassword{
return true
} else {
return false
}
}
// validating Blank Text........................
var isBlank:Bool {
return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty
}
}
But when i am trying to use this extension class in other view controller through the code :
if isValidEmail("kirit#gmail.com"){
print("Validate EmailID")
}
else{
print("invalide EmailID")
}
I am getting the error:

isValidEmail is not a loose function. You have defined it as an instance function on String. You would need to say
"someString".isValidEmail(testStr:"someOtherString")
That makes no sense, but that's how you've configured it. If you write it that way, your code will compile (though it will be very silly code).

Just change your definition to something like
extension String {
// Validating Email ID
func isValidEmail() -> Bool {
self.validate...
}
and then use it in your code as
#someString".isValidEmail()

Related

Value of type '[String]' has no member 'replacingOccurrences'?

I have a problem
Value of type [String] has no member replacingOccurrences
When I call replacingOccurrences it does not seem to work for Array types.
func translateEquipement(equipment: [String]) -> [String]{
var result = equipment
let equipmentsSonoVideo = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
for equipmentSonoVideo in equipmentsSonoVideo {
result = result.replacingOccurrences(of: equipmentSonoVideo, with: NSLocalizedString(equipmentSonoVideo, comment: "") options: NSString.CompareOptions.literal, range: nil)
}
return result
}
My function below is for showing equipment:
func showEquipments(place: Place) {
var sonoShowed = false
var videoShowed = false
if let equipments = place.equipments {
for equipment in equipments {
// equipement
if equipment == "Sono" {
// equipement sono
self.sonoLabel.text = equipment
sonoShowed = true
}
else if equipment == "Video projecteur" {
// equipement video
self.videoLabel.text = equipment
videoShowed = true
}
}
}
if !sonoShowed {
self.sonoContainer.isHidden = true
}
if !videoShowed {
self.videoContainer.isHidden = true
}
}
As the error says you can not use that method on an array so you need to access each string in the array individually. I did this using map like this
func translateEquipement(equipment: [String]) -> [String]{
var result = equipment
let equipmentsSonoVideo = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
for item in equipmentsSonoVideo {
result = result.map {
$0.replacingOccurrences(of: item,
with: NSLocalizedString(item, comment: ""),
options: NSString.CompareOptions.literal, range: nil)
}
}
return result
}
If you like, you can create an extension for [String] so you can use replacingOccurrences as you are in your current code:
extension Array where Element == String {
func replacingOccurrences(of target: String, with replacement: String, options: NSString.CompareOptions) -> Self
{
return self.map( { $0.replacingOccurrences(of: target, with: replacement, options: options) } )
}
}
If you require the NSRange parameter that you're specifying nil for simply add it.

RxSwift convert Observable<String> into String

I've having a problem in RxSwift in converting a Observable into a String.
I understand that an observable is a sequence, I just want the last change in the username.
I'm storing username and password as:
let username = BehaviorRelay<String>(value: "")
let password = BehaviorRelay<String>(value: "")
And previously have used combineLatest
func loginButtonValid(username: Observable<String>, password: Observable<String>) -> Observable<Bool> {
return Observable.combineLatest(username, password)
{ (username, password) in
return username.count > 0
&& password.count > 0
&& self.validateEmail(enteredEmail: username)
}
}
But how can I just take the latest from the username?
I've tried takelast and use combineLatest with just one argument, but neither seems to work.
I want to do this to validate whether an email is valid, and the validate email function I'm using is the following (for reference only):
func validateEmail(enteredEmail:String) -> Bool {
let emailFormat = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format:"SELF MATCHES %#", emailFormat)
return emailPredicate.evaluate(with: enteredEmail)
}
My attempts are around the following ideas
func emailValid(username: Observable<String>) -> Observable<Bool> {
return username.takeLast()
{ (lastusername) in
return self.validateEmail(enteredEmail: (lastusername) )
}
}
If I understood correctly, you want to transform an Observable<String> to an Observable<Bool>, which is the job of the map method:
func emailValid(username: Observable<String>) -> Observable<Bool> {
return username.map(self.validateEmail)
}

Adding a rule to a custom rule in Eureka

So I've created a custom row which is just a row with a simply UITextView now I want to create a rule such that if the UITextView has under 100 characters the variable row.isValid will evaluate to false.
<<< TextViewRow("About Me") {
let cell = $0.baseCell as! TextViewCell
cell.textView.text = currentUser.aboutMe
$0.disabled = Condition.function([]) {
form in
print("in disabled")
let section = form.sectionBy(tag: "About Me")
let view = section!.header?.viewForSection(section!, type: .header) as! TitleHeaderView
if view.isLocked {
return true
} else {
return false
}
}
$0.cellUpdate({ (cell, row) in
if row.isDisabled {
cell.textView.isUserInteractionEnabled = false
cell.textView.textColor = UIColor.gray
} else {
cell.textView.isUserInteractionEnabled = true
cell.textView.textColor = UIColor.black
}
})
let ruleRequiredViaClosure = RuleClosure<String> { rowValue in
guard let rowValue = rowValue else {
return(ValidationError(msg : "Please write more!"))
}
let numberOfCharacters = rowValue.characters.count
return (numberOfCharacters < 250 ? ValidationError(msg: "Please write more!") : nil)
}
$0.add(rule: ruleRequiredViaClosure)
$0.validationOptions = .validatesOnDemand
}
With a normal TextRow this would compile and XCode won't throw any error, although since TextViewRow is a custom row I believe this is why it is throwing an error.
The error it gives me is that I need to use:
$0.add(ruleSet: ...)
but I can't find any documentation on it.
Also I'm not sure whether I am able to define my rule as I have since the rowValue in the closure probably isn't referencing anything. How would I fix this problem?
Welcome to the world of Eureka, %&*# all documentation on anything. I recently took a liking to Eureka and began to convert my project to it, along the way I hit many obstacles that could easily have been avoided if the documentation was decent. Fortunately for you, custom validation rules was one such obstacle; my implementation is as follows:
struct RulePassword<T: Equatable>:RuleType {
public init() {}
public var id: String?
public var validationError: ValidationError = ValidationError(msg: "Invalid password.")
public func isValid(value: T?) -> ValidationError? {
if let str = value as? String {
let errorMsg = RGOValidationHelper.passwordValidation(str)
return errorMsg != nil ? ValidationError(msg: errorMsg!) : nil
}
return validationError
}
}
Note the call to RGOValidationHelper simply returns an error string in the event that the validation fails, nil otherwise.
Before you go and create a custom rule however, it must be noted that Eureka actually includes a facility to set a minimum length on a field. RuleMinLength is the name of the rule and it can be initialised by passing in a value for min length, in your case 100. Here is Eureka's implementation for the rule:
public struct RuleMinLength: RuleType {
let min: UInt
public var id: String?
public var validationError: ValidationError
public init(minLength: UInt, msg: String? = nil){
let ruleMsg = msg ?? "Field value must have at least \(minLength) characters"
min = minLength
validationError = ValidationError(msg: ruleMsg)
}
public func isValid(value: String?) -> ValidationError? {
guard let value = value else { return nil }
return value.characters.count < Int(min) ? validationError : nil
}
}
I hope this helps somewhat, if you have any further problems with Eureka chances are I've come across and solved them before, so feel free to send me a message.

How to remove duplicate characters from a string in Swift

ruby has the function string.squeeze, but I can't seem to find a swift equivalent.
For example I want to turn bookkeeper -> bokepr
Is my only option to create a set of the characters and then pull the characters from the set back to a string?
Is there a better way to do this?
Edit/update: Swift 4.2 or later
You can use a set to filter your duplicated characters:
let str = "bookkeeper"
var set = Set<Character>()
let squeezed = str.filter{ set.insert($0).inserted }
print(squeezed) // "bokepr"
Or as an extension on RangeReplaceableCollection which will also extend String and Substrings as well:
extension RangeReplaceableCollection where Element: Hashable {
var squeezed: Self {
var set = Set<Element>()
return filter{ set.insert($0).inserted }
}
}
let str = "bookkeeper"
print(str.squeezed) // "bokepr"
print(str[...].squeezed) // "bokepr"
I would use this piece of code from another answer of mine, which removes all duplicates of a sequence (keeping only the first occurrence of each), while maintaining order.
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var alreadyAdded = Set<Iterator.Element>()
return self.filter { alreadyAdded.insert($0).inserted }
}
}
I would then wrap it with some logic which turns a String into a sequence (by getting its characters), unqiue's it, and then restores that result back into a string:
extension String {
func uniqueCharacters() -> String {
return String(self.characters.unique())
}
}
print("bookkeeper".uniqueCharacters()) // => "bokepr"
Here is a solution I found online, however I don't think it is optimal.
func removeDuplicateLetters(_ s: String) -> String {
if s.characters.count == 0 {
return ""
}
let aNum = Int("a".unicodeScalars.filter{$0.isASCII}.map{$0.value}.first!)
let characters = Array(s.lowercased().characters)
var counts = [Int](repeatElement(0, count: 26))
var visited = [Bool](repeatElement(false, count: 26))
var stack = [Character]()
var i = 0
for character in characters {
if let num = asciiValueOfCharacter(character) {
counts[num - aNum] += 1
}
}
for character in characters {
if let num = asciiValueOfCharacter(character) {
i = num - aNum
counts[i] -= 1
if visited[i] {
continue
}
while !stack.isEmpty, let peekNum = asciiValueOfCharacter(stack.last!), num < peekNum && counts[peekNum - aNum] != 0 {
visited[peekNum - aNum] = false
stack.removeLast()
}
stack.append(character)
visited[i] = true
}
}
return String(stack)
}
func asciiValueOfCharacter(_ character: Character) -> Int? {
let value = String(character).unicodeScalars.filter{$0.isASCII}.first?.value ?? 0
return Int(value)
}
Here is one way to do this using reduce(),
let newChar = str.characters.reduce("") { partial, char in
guard let _ = partial.range(of: String(char)) else {
return partial.appending(String(char))
}
return partial
}
As suggested by Leo, here is a bit shorter version of the same approach,
let newChar = str.characters.reduce("") { $0.range(of: String($1)) == nil ? $0.appending(String($1)) : $0 }
Just Another solution
let str = "Bookeeper"
let newChar = str.reduce("" , {
if $0.contains($1) {
return "\($0)"
} else {
return "\($0)\($1)"
}
})
print(str.replacingOccurrences(of: " ", with: ""))
Use filter and contains to remove duplicate values
let str = "bookkeeper"
let result = str.filter{!result.contains($0)}
print(result) //bokepr

How to check is a string or number?

I have an array ["abc", "94761178","790"]
I want to iterate each and check is a String or an Int?
How to check it?
How to convert "123" to integer 123?
Here is a small Swift version using String extension :
Swift 3/Swift 4 :
extension String {
var isNumber: Bool {
return !isEmpty && rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil
}
}
Swift 2 :
extension String {
var isNumber : Bool {
get{
return !self.isEmpty && self.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) == nil
}
}
}
Edit Swift 2.2:
In swift 2.2 use Int(yourArray[1])
var yourArray = ["abc", "94761178","790"]
var num = Int(yourArray[1])
if num != nil {
println("Valid Integer")
}
else {
println("Not Valid Integer")
}
It will show you that string is valid integer and num contains valid Int.You can do your calculation with num.
From docs:
If the string represents an integer that fits into an Int, returns the
corresponding integer.This accepts strings that match the regular
expression "[-+]?[0-9]+" only.
Be aware that checking a string/number using the Int initializer has limits. Specifically, a max value of 2^32-1 or 4294967295. This can lead to problems, as a phone number of 8005551234 will fail the Int(8005551234) check despite being a valid number.
A much safer approach is to use NSCharacterSet to check for any characters matching the decimal set in the range of the string.
let number = "8005551234"
let numberCharacters = NSCharacterSet.decimalDigitCharacterSet().invertedSet
if !number.isEmpty && number.rangeOfCharacterFromSet(numberCharacters) == nil {
// string is a valid number
} else {
// string contained non-digit characters
}
Additionally, it could be useful to add this to a String extension.
public extension String {
func isNumber() -> Bool {
let numberCharacters = NSCharacterSet.decimalDigitCharacterSet().invertedSet
return !self.isEmpty && self.rangeOfCharacterFromSet(numberCharacters) == nil
}
}
I think the nicest solution is:
extension String {
var isNumeric : Bool {
return Double(self) != nil
}
}
Starting from Swift 2, String.toInt() was removed.
A new Int Initializer was being introduced: Int(str: String)
for target in ["abc", "94761178","790"]
{
if let number = Int(target)
{
print("value: \(target) is a valid number. add one to get :\(number+1)!")
}
else
{
print("value: \(target) is not a valid number.")
}
}
Swift 3, 4
extension String {
var isNumber: Bool {
let characters = CharacterSet.decimalDigits.inverted
return !self.isEmpty && rangeOfCharacter(from: characters) == nil
}
}
Simple solution like this:
extension String {
public var isNumber: Bool {
return !isEmpty && rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil
}
}
I think using NumberFormatter is an easy way:
(Swift 5)
import Foundation
extension String {
private static let numberFormatter = NumberFormatter()
var isNumeric : Bool {
Self.numberFormatter.number(from: self) != nil
}
}
The correct way is to use the toInt() method of String, and an optional binding to determine whether the conversion succeeded or not. So your loop would look like:
let myArray = ["abc", "94761178","790"]
for val in myArray {
if let intValue = val.toInt() {
// It's an int
println(intValue)
} else {
// It's not an int
println(val)
}
}
The toInt() method returns an Int?, so an optional Int, which is nil if the string cannot be converted ton an integer, or an Int value (wrapped in the optional) if the conversion succeeds.
The method documentation (shown using CMD+click on toInt in Xcode) says:
If the string represents an integer that fits into an Int, returns the corresponding integer. This accepts strings that match the regular expression "[-+]?[0-9]+" only.
This way works also with strings with mixed numbers:
public extension String {
func isNumber() -> Bool {
return !self.isEmpty && self.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil && self.rangeOfCharacter(from: CharacterSet.letters) == nil
}}
So u get something like this:
Swift 3.0 version
func isNumber(stringToTest : String) -> Bool {
let numberCharacters = CharacterSet.decimalDigits.inverted
return !s.isEmpty && s.rangeOfCharacter(from:numberCharacters) == nil
}
If you want to accept a more fine-grained approach (i.e. accept a number like 4.5 or 3e10), you proceed like this:
func isNumber(val: String) -> Bool
{
var result: Bool = false
let parseDotComNumberCharacterSet = NSMutableCharacterSet.decimalDigitCharacterSet()
parseDotComNumberCharacterSet.formUnionWithCharacterSet(NSCharacterSet(charactersInString: ".e"))
let noNumberCharacters = parseDotComNumberCharacterSet.invertedSet
if let v = val
{
result = !v.isEmpty && v.rangeOfCharacterFromSet(noNumberCharacters) == nil
}
return result
}
For even better resolution, you might draw on regular expression..
Xcode 8 and Swift 3.0
We can also check :
//MARK: - NUMERIC DIGITS
class func isString10Digits(ten_digits: String) -> Bool{
if !ten_digits.isEmpty {
let numberCharacters = NSCharacterSet.decimalDigits.inverted
return !ten_digits.isEmpty && ten_digits.rangeOfCharacter(from: numberCharacters) == nil
}
return false
}
This code works for me for Swift 3/4
func isNumber(textField: UITextField) -> Bool {
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: textField.text!)
return allowedCharacters.isSuperset(of: characterSet)
// return true
}
You can use this for integers of any length.
func getIntegerStrings(from givenStrings: [String]) -> [String]
{
var integerStrings = [String]()
for string in givenStrings
{
let isValidInteger = isInteger(givenString: string)
if isValidInteger { integerStrings.append(string) }
}
return integerStrings
}
func isInteger(givenString: String) -> Bool
{
var answer = true
givenString.forEach { answer = ("0"..."9").contains($0) && answer }
return answer
}
func getIntegers(from integerStrings: [String]) -> [Int]
{
let integers = integerStrings.compactMap { Int($0) }
return integers
}
let strings = ["abc", "94761178", "790", "18446744073709551615000000"]
let integerStrings = getIntegerStrings(from: strings)
let integers = getIntegers(from: integerStrings)
print(integerStrings) // ["94761178", "790", "18446744073709551615000000"]
print(integers) // [94761178, 790]
However, as pointed out by #Can, you can get the integer value for the number only up to 2^31 - 1 (signed integer limit on 32-bit arch). For the larger value, however, you will still get the string representation.
This code will return an array of converted integers:
["abc", "94761178","790"].map(Int.init) // returns [ nil, 94761178, 790 ]
OR
["abc", "94761178","790"].map { Int($0) ?? 0 } // returns [ 0, 94761178, 790 ]
Get the following isInteger() function from the below stackoverflow post posted by corsiKa:
Determine if a String is an Integer in Java
And I think this is what you want to do (where nameOfArray is the array you want to pass)
void convertStrArrayToIntArray( int[] integerArray ) {
for (int i = 0; i < nameOfArray.length(); i++) {
if (!isInteger(nameOfArray[i])) {
integerArray[i] = nameOfArray[i].toString();
}
}
}