Validate URL without scheme - ios

Swift 5, Xcode 10, iOS 12
My code uses UIApplication.shared.canOpenURL to validate URLs, which unfortunately fails without e.g. "http://".
Example:
print(UIApplication.shared.canOpenURL(URL(string: "stackoverflow.com")!)) //false
print(UIApplication.shared.canOpenURL(URL(string: "http://stackoverflow.com")!)) //true
print(UIApplication.shared.canOpenURL(URL(string: "129.0.0.1")!)) //false
print(UIApplication.shared.canOpenURL(URL(string: "ftp://129.0.0.1")!)) //true
I'm aware of the change with schemes (iOS9+) and I know that I can just add a prefix like "http://" if the String doesn't start with it already, then check this new String but I'm still wondering:
Question: How do I add a "there's no scheme" scheme, so valid URLs like "stackoverflow.com" return true too (is this even possible?)?

It's not possible to add a valid scheme to URL because no one knows which prefix will be add to which URL. You can just validate a URL with the help of regex.
I searched and modified the regex.
extension String {
func isValidUrl() -> Bool {
let regex = "((http|https|ftp)://)?((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+"
let predicate = NSPredicate(format: "SELF MATCHES %#", regex)
return predicate.evaluate(with: self)
}
}
I tested it with below urls:
print("http://stackoverflow.com".isValidUrl())
print("stackoverflow.com".isValidUrl())
print("ftp://127.0.0.1".isValidUrl())
print("www.google.com".isValidUrl())
print("127.0.0.1".isValidUrl())
print("127".isValidUrl())
print("hello".isValidUrl())
Output
true
true
true
true
true
false
false
Note: 100% regex is not possible to validate the email and url

This is the method that I use
extension String {
/// Return first available URL in the string else nil
func checkForURL() -> NSRange? {
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return nil
}
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count))
for match in matches {
guard Range(match.range, in: self) != nil else { continue }
return match.range
}
return nil
}
func getURLIfPresent() -> String? {
guard let range = self.checkForURL() else{
return nil
}
guard let stringRange = Range(range,in:self) else {
return nil
}
return String(self[stringRange])
}
}
Apparently, the method name and the comment in the code are not verbose enough, so here is the explanation.
Used NSDataDetector and provided it the type - NSTextCheckingResult.CheckingType.link to check for links.
This goes through the string provided and returns all the matches for URL type.
This checks for link in the string that you provide, if any, else returns nil.
The method getURLIfPresent return the URL part from that string.
Here are a few examples
print("http://stackoverflow.com".getURLIfPresent())
print("stackoverflow.com".getURLIfPresent())
print("ftp://127.0.0.1".getURLIfPresent())
print("www.google.com".getURLIfPresent())
print("127.0.0.1".getURLIfPresent())
print("127".getURLIfPresent())
print("hello".getURLIfPresent())
Output
Optional("http://stackoverflow.com")
Optional("stackoverflow.com")
Optional("ftp://127.0.0.1")
Optional("www.google.com")
nil
nil
nil
But, this doesn't return true for "127.0.0.1". So I don't think it will fulfil your cause.
In your case, going the regex way is better it seems. As you can add more conditions if you come across some more patterns that demand to be considered as URL.

Related

SWIFT checking if a string begins with something but the second character can be anything

I'm new to using swift so I'm playing around with it and I'm trying to figure out how to check if a given string begins with the word "hello" though the second letter can be anything it doesn't have to be an E so I could type h0llo and it would still return true is what I'm looking to achieve.
This is the code I have so far however, it is jumbled all over the place from testing, so any help would be necessary at this stage for me. I am only adding the code so that you could understand more what it is im trying to achieve
func check(_ givenString: String) -> Bool {
var newString = givenString
if newString.count > 2 {
newString.remove(at: String.Index(encodedOffset: 2))
if newString.hasPrefix("hello") {
return true
} else {
return false
}
}
}
print(check("h0llo"))
A reasonable solution is to check with Regular Expression
func check(_ givenString: String) -> Bool {
return givenString.range(of: "^h.llo", options: .regularExpression) != nil
}
The caret ^ checks for beginning of the string and the dot . represents any character.

Use RegEx to validate a password on iOS Swift

I need to validate a user's password against the following requirements:
8 or more characters.
Contains 1 character and 1 number
Can enter letters, numbers, and symbols
Does anyone know how I can accomplish this using a RegEx?
I've made attempts to solve this problem on my own, but nothing I've tried so far as worked. The code for my latest attempt is below.
func isPasswordHasEightCharacter(password: String) -> Bool {
let passWordRegEx = "^.{8,}$"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passWordRegEx)
return passwordTest.evaluate(with: password)
}
func isPasswordHasNumberAndCharacter(password: String) -> Bool {
let passRegEx = "^(?=.*[a-z])(?=.*[0-9])"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passRegEx)
return passwordTest.evaluate(with: password)
}
func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
let passWordRegEx = "^(?!.[^a-zA-Z0-9##${'$'}^+=])"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passWordRegEx)
return passwordTest.evaluate(with: password)
}
In this solution each requirement is checked individually to avoid complex regular expressions. This solution supports variants of characters like ôöệż etc
func validatePassword(_ password: String) -> Bool {
//At least 8 characters
if password.count < 8 {
return false
}
//At least one digit
if password.range(of: #"\d+"#, options: .regularExpression) == nil {
return false
}
//At least one letter
if password.range(of: #"\p{Alphabetic}+"#, options: .regularExpression) == nil {
return false
}
//No whitespace charcters
if password.range(of: #"\s+"#, options: .regularExpression) != nil {
return false
}
return true
}
Some test cases
print(validatePassword("abc")) // --> false
print(validatePassword("abcdefgh")) // --> false
print(validatePassword("abcde fgh1")) // --> false
print(validatePassword("abcdefgh1")) // --> true
print(validatePassword("abcåäö123")) // --> true
print(validatePassword("ABC123€%&")) // --> true
print(validatePassword("#èệżôøö123")) // --> true
The main issue is that NSPredicate with MATCHES requires the full string to match and consume the whole input. Lookarounds - you are using lookaheads - do not consume text, that is, the texts they match are not added to the match value and the regex index stays where it was before attempting to match a lookaround pattern.
The last two parts can be thus fixed this way:
func isPasswordHasNumberAndCharacter(password: String) -> Bool {
let passRegEx = "(?=[^a-z]*[a-z])[^0-9]*[0-9].*"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passRegEx)
return passwordTest.evaluate(with: password)
}
func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
let passWordRegEx = "[a-zA-Z0-9!##$%^&*]+"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passWordRegEx)
return passwordTest.evaluate(with: password)
}
The first part is OK, though you do not need the ^ and $ anchors (as the whole string input must match the pattern). However, to check a string length you do not even need a regex: see Get the length of a String.
Note:
^(?=[^a-z]*[a-z])[^0-9]*[0-9].*\z matches a string that contains at least one lowercase ASCII letter and at least one ASCII digit
^[a-zA-Z0-9!##$%^&*]+$ will match a string that only contains ASCII letters, digits and some specific symbols. If you inlude a - make sure it is at the end. Escape both [ and ] if you need to add them, too.
If you want to combine all that into 1 regex you could use
let passRegEx = "(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!##$%^&*]{8,}"
Or, if you are not using the regex with the NSPredicate MATCHES, with anchors:
let passRegEx = "\\A(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!##$%^&*]{8,}\\z"

How to get full display name for locale

I want to show "繁體中文", "简体中文" in a view.
I use Locale.displayname to get displayname and my parameter is "zh-Hant" and "zh-Hans", the value will return "中文(繁體)"and "中文(简体)".
Here is parts of my code:
let loacleName = locale.displayName(forKey: NSLocale.Key.identifier, value: "zh-Hant")
Is anyone know how can I get "繁體中文", "简体中文" from iOS function?
This is a solution using native Locale. For the identifiers zh-Hant and zh-Hans it removes the unwanted characters with Regular Expression (be aware that the characters are not parentheses and space characters) and swaps both pairs.
extension Locale {
var localizedFullDisplayName : String? {
if self.identifier.hasPrefix("zh-Han") {
guard let trimmed = self.localizedString(forIdentifier: self.identifier)?.replacingOccurrences(of: "[()]", with: "", options: .regularExpression) else { return nil }
return String(trimmed.suffix(2) + trimmed.prefix(2))
} else {
return self.localizedString(forIdentifier: locale.identifier)
}
}
}
let locale = Locale(identifier: "zh-Hans")
locale.localizedFullDisplayName

backspace not work in outside of regex in swift

I use this method for patterning the phone number in UITextField at the .editingChange event
But the delete key only removes the numbers
extension String{
func applyPatternOnNumbers(pattern: String) -> String {
let replacmentCharacter: Character = "#"
let pureNumber = self.replacingOccurrences( of: "[^۰-۹0-9]", with: "", options: .regularExpression)
var result = ""
var pureNumberIndex = pureNumber.startIndex
for patternCharacter in pattern {
if patternCharacter == replacmentCharacter {
guard pureNumberIndex < pureNumber.endIndex else { return result }
result.append(pureNumber[pureNumberIndex])
pureNumber.formIndex(after: &pureNumberIndex)
} else {
result.append(patternCharacter)
}
}
return result
}
}
use at the editingChange event
let pattern = "+# (###) ###-####"
let mobile = textField.text.substring(to: pattern.count-1)
textfield.text = mobile.applyPatternOnNumbers(pattern: pattern)
// print(textfield.text) +1 (800) 666-8888
the problem is space & - , ( , ) chars can not to be removed
The RegEx you are trying is to not consider digits only:
[^۰-۹0-9]
I'm not sure, but you may change it to:
[^۰-۹0-9\s-\(\)]
and it may work. You might just add a \ before your special chars inside [] and you can any other chars into it that you do not need to be replaced.
Or you may simplify it to
[^\d\s-\(\)]
and it might work.
Method 2
You may use this RegEx which is an exact match to the phone number format you are having:
\+\d+\s\(\d{3}\)\s\d{3}-\d{4}
You may remove the first +, if it is unnecessary
\d+\s\(\d{3}\)\s\d{3}-\d{4}

Swift: how to censor/filter text entered for swear words, etc?

I just want to see whether there is an established way to do this, or how one would go about this.
I have a text field that essentially acts as a form in my iOs app where a user can post something. I can't have users posting swear words/inappropriate crap, so I want to filter out and display an error if the string they enter contains one of these words.
How do other apps in Swift do this? Do they just search through the string to see if it contains the word (obviously not within other words, but standing alone) or is there another method?
How can I accurately filter out the swear words from my user post in Swift?
Construct a list of words you consider to be swear words, and simply check the user entered string whether any of these words are contained within the string.
Swift 3:
import Foundation
func containsSwearWord(text: String, swearWords: [String]) -> Bool {
return swearWords
.reduce(false) { $0 || text.contains($1.lowercased()) }
}
// example usage
let listOfSwearWords = ["darn", "crap", "newb"]
/* list as lower case */
let userEnteredText1 = "This darn didelo thread is a no no."
let userEnteredText2 = "This fine didelo thread is a go."
print(containsSwearWord(text: userEnteredText1, swearWords: listOfSwearWords)) // true
print(containsSwearWord(text: userEnteredText2, swearWords: listOfSwearWords)) // false
Swift 2.2:
import Foundation
func containsSwearWord(text: String, swearWords: [String]) -> Bool {
return swearWords
.reduce(false) { $0 || text.containsString($1.lowercaseString) }
}
// example usage
let listOfSwearWords = ["darn", "crap", "newb"]
/* list as lower case */
let userEnteredText1 = "This darn didelo thread is a no no."
let userEnteredText2 = "This fine didelo thread is a go."
print(containsSwearWord(userEnteredText1, swearWords: listOfSwearWords)) // true
print(containsSwearWord(userEnteredText2, swearWords: listOfSwearWords)) // false
I created a class that enables you to feed a string in and remove profanity. Here's a link to the repo.
Here's the code:
class ProfanityFilter: NSObject {
static let sharedInstance = ProfanityFilter()
private override init() {}
// Customize as needed
private let dirtyWords = "\\b(ducker|mother ducker|motherducker|shot|bad word|another bad word|)\\b"
// Courtesy of Martin R
// https://stackoverflow.com/users/1187415/martin-r
private func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [.caseInsensitive])
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
public func cleanUp(_ string: String) -> String {
let dirtyWords = matches(for: self.dirtyWords, in: string)
if dirtyWords.count == 0 {
return string
} else {
var newString = string
dirtyWords.forEach({ dirtyWord in
let newWord = String(repeating: "😲", count: dirtyWord.characters.count)
newString = newString.replacingOccurrences(of: dirtyWord, with: newWord, options: [.caseInsensitive])
})
return newString
}
}
}
Usage:
yourLabel.text = ProfanityFilter.sharedInstance.cleanUp(yourString)
Extension checking for foul language.
Swift 4.2
Example Usage:
"poop".containsBadWord()
Extension:
extension String {
func containsBadWord()->Bool {
//Sorry for bad words
let badWords = ["insert","bad","words","here","poop"]
for word in badWords {
if lowercased().contains(word) {
return true
}
}
return false
}
}
I would suggest looking into an API to which you can submit a string and get a JSON response containing information such as:
Is the string bad?
Total # of bad words contained in string
An array containing all recognized bad words
A censored version of the input string
I found a couple sources via Google. Check these out and do a little more research to find if an API is the best fit for you and which one you should use. I would assume that using an API like the one I have listed below would be the most practical approach, as you would NOT have to compile a list of "bad" words yourself and use resources from the device to sort through the list (which can contain thousands of words).
Rather, you can simply submit a string using the API to get a network response containing the data in JSON format from the API server.
Why not let the API Server do the logic for you and just spit out an answer?
NeutrinoAPI
If this method returns a range,
str.range(of: "darn|crap|newb", options: [.regularExpressionSearch, .caseInsensitiveSearch], range: str.startIndex..<str.endIndex, locale:nil)
an offensive word has been found. While this method can be used to remove the offending strings:
str.replacingOccurrences(of: "darn|crap|newb", with: "", options: [.regularExpressionSearch, .caseInsensitiveSearch])
If you filter bad words locally you wouldn’t easily accommodate many languages, also as new bad words appear, you would have to waste a developers time manually putting in bad words, alternatively, there are apis designed for this purpose: https://www.moderatecontent.com/documentation/badwords

Resources