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

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

Related

SwiftUI Can't Pass String to KeyPath

I have an app that references a large dataset that I load from an external public
site as a comma separated value file. I parse the data to a an array of a model called
WaterPointModel. An abbreviated version is:
struct WaterPointModel: Identifiable {
let id = UUID()
let STATE: String
let COUNTY: String
let AQWFrTo: Double
let AQWGWSa: Double
let AQWGWTo: Double
//many more
}
I then want to summarize(reduce) the data. My function for this is:
func sumOnAttributeUSList(sumField: KeyPath<WaterPointModel,Double>) -> Double {
return dataStore.waterPointModels.map({ $0[keyPath:sumField] }).reduce(0, +)
}
Next I want to call this to build a report:
let aqWFrTo = sumOnAttributeUSList(sumField: \.AQWFrTo)
This all works. However there are other reports where I need to pass a string to
create that keypath. Say I have a lookup table where I lookup "abc" and get "AQWFrTo". I
would like to do something like this in a loop:
let abc = "AQWFrTo"
let aqWFrTo = sumOnAttributeUSList(sumField: \WaterPointModel.abc)
I have not been able to make any version of this work. Any guidance would be appreciated.
Xcode 13.3.1, iOS 15.4
A simple approach is this:
func toKeypath(_ str: String) -> KeyPath<WaterPointModel,Double>? { // <-- with or without optional
switch str {
case "AQWFrTo": return \.AQWFrTo
case "AQWGWSa": return \.AQWGWSa
case "AQWGWTo": return \.AQWGWTo
// ...
default: return nil // <-- or a default Keypath
}
}
let aqWFrTo = sumOnAttributeUSList(sumField: toKeypath("AQWFrTo"))

NSNetService dictionaryFromTXTRecord fails an assertion on invalid input

The input to dictionary(fromTXTRecord:) comes from the network, potentially from outside the app, or even the device. However, Apple's docs say:
... Fails an assertion if txtData cannot be represented as an NSDictionary object.
Failing an assertion leaves the programmer (me) with no way of handling the error, which seems illogic for a method that processes external data.
If I run this in Terminal on a Mac:
dns-sd -R 'My Service Name' _myservice._tcp local 4567 asdf asdf
my app, running in an iPhone, crashes.
dictionary(fromTXTRecord:) expects the TXT record data (asdf asdf) to be in key=val form. If, like above, a word doesn't contain any = the method won't be able to parse it and fail the assertion.
I see no way of solving this problem other than not using that method at all and implementing my own parsing, which feels wrong.
Am I missing something?
Here's a solution in Swift 4.2, assuming the TXT record has only strings:
/// Decode the TXT record as a string dictionary, or [:] if the data is malformed
public func dictionary(fromTXTRecord txtData: Data) -> [String: String] {
var result = [String: String]()
var data = txtData
while !data.isEmpty {
// The first byte of each record is its length, so prefix that much data
let recordLength = Int(data.removeFirst())
guard data.count >= recordLength else { return [:] }
let recordData = data[..<(data.startIndex + recordLength)]
data = data.dropFirst(recordLength)
guard let record = String(bytes: recordData, encoding: .utf8) else { return [:] }
// The format of the entry is "key=value"
// (According to the reference implementation, = is optional if there is no value,
// and any equals signs after the first are part of the value.)
// `ommittingEmptySubsequences` is necessary otherwise an empty string will crash the next line
let keyValue = record.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false)
let key = String(keyValue[0])
// If there's no value, make the value the empty string
switch keyValue.count {
case 1:
result[key] = ""
case 2:
result[key] = String(keyValue[1])
default:
fatalError()
}
}
return result
}
I'm still hoping there's something I'm missing here, but in the mean time, I ended up checking the data for correctness and only then calling Apple's own method.
Here's my workaround:
func dictionaryFromTXTRecordData(data: NSData) -> [String:NSData] {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes), count: data.length)
var pos = 0
while pos < buffer.count {
let len = Int(buffer[pos])
if len > (buffer.count - pos + 1) {
return [:]
}
let subdata = data.subdataWithRange(NSRange(location: pos + 1, length: len))
guard let substring = String(data: subdata, encoding: NSUTF8StringEncoding) else {
return [:]
}
if !substring.containsString("=") {
return [:]
}
pos = pos + len + 1
}
return NSNetService.dictionaryFromTXTRecordData(data)
}
I'm using Swift 2 here. All contributions are welcome. Swift 3 versions, Objective-C versions, improvements, corrections.
I just ran into this one using Swift 3. In my case the problem only occurred when I used NetService.dictionary(fromTXTRecord:) but did not occur when I switched to Objective-C and called NSNetService dictionaryFromTXTRecord:. When the Objective-C call encounters an entry without an equal sign it creates a key containing the data and shoves it into the dictionary with an NSNull value. From what I can tell the Swift version then enumerates that dictionary and throws a fit when it sees the NSNull. My solution was to add an Objective-C file and a utility function that calls dictionaryFromTXTRecord: and cleans up the results before handing them back to my Swift code.

Recreating Python's input statement in Swift

I was trying to recreate Python's input() statement in Swift, I have seen some examples, but I am trying to make it better, firstly, my version removes the \n part of the string, also, I was trying to make it firstly print a prompt, so that var example = input() would just wait for the message, (which it does), but then var example = input("Enter text: ") would print Enter text: and wait for text to be inputed.
The problem is, swift seems to be messing up the print's order. For example, being the code:
import Foundation
func input(inputStatement: String? = nil) -> String {
if let inputStatement = inputStatement {
print(inputStatement, terminator:"")
}
let keyboard = NSFileHandle.fileHandleWithStandardInput()
let inputData = keyboard.availableData
var strData = NSString(data: inputData, encoding: NSUTF8StringEncoding) as! String
strData = strData.stringByReplacingOccurrencesOfString("\n", withString: "")
print()
return strData
}
print("Creating the input statement in Swift!")
var test = input("What's your name: ")
print("You entered: \(test).")
And the input text, "hi", this prints:
Creating the input statement in Swift!
hi
What's your name: You entered: hi.
And what I expected was:
Creating the input statement in Swift!
What's your name: hi
You entered: hi.
What am I missing here?
Thanks
The problem is that the standard output file descriptor is line buffered
when writing to a terminal (and fully buffered otherwise).
Therefore the output of
print(inputStatement, terminator:"")
is buffered and not written before the
print()
writes a newline. You can fix that by flushing the file
descriptor explicitly:
if let inputStatement = inputStatement {
print(inputStatement, terminator:"")
fflush(stdout)
}
Note also that there is a
public func readLine(stripNewline stripNewline: Bool = default) -> String?
which reads a line from standard input, with the option to
remove the trailing newline character. This function also
flushes standard output. Therefore a simpler implementation would be
func input(prompt: String = "") -> String {
print(prompt, terminator: "")
guard let reply = readLine(stripNewline: true) else {
fatalError("Unexpected EOF on input")
}
return reply
}
(Of course you might choose to handle "end of file" differently.)

Replace part of string with lower case letters - Swift

I have a Swift based iOS app and one of the features allows you to comment on a post. Anyway, users can add "#mentions" in their posts to tag other people. However I want to stop the user from adding a username with a capital letter.
Is there anyway I can convert a string, so that the #usernames are all in lowercase?
For example:
I really enjoy sightseeing with #uSerABC (not allowed)
I really enjoy sightseeing with #userabc (allowed)
I know there is a property for the string in swift called .lowercaseString - but the problem with that, is that it makes the entire string lowercase and thats not what I want. I only want the #username to be in lower case.
Is there any way around this with having to use the .lowercase property.
Thanks for your time, Dan.
This comes from a code I use to detect hashtags, I've modified to detect mentions:
func detectMentionsInText(text: String) -> [NSRange]? {
let mentionsDetector = try? NSRegularExpression(pattern: "#(\\w+)", options: NSRegularExpressionOptions.CaseInsensitive)
let results = mentionsDetector?.matchesInString(text, options: NSMatchingOptions.WithoutAnchoringBounds, range: NSMakeRange(0, text.utf16.count)).map { $0 }
return results?.map{$0.rangeAtIndex(0)}
}
It detects all the mentions in a string by using a regex and returns an NSRange array, by using a range you have the beginning and the end of the "mention" and you can easily replace them with a lower case version.
Split the string into two using the following command -
let arr = myString.componentsSeparatedByString("#")
//Convert arr[1] to lower case
//Append to arr[0]
//Enjoy
Thanks to everyone for their help. In the end I couldn't get any of the solutions to work and after a lot of testing, I came up with this solution:
func correctStringWithUsernames(inputString: String, completion: (correctString: String) -> Void) {
// Create the final string and get all
// the seperate strings from the data.
var finalString: String!
var commentSegments: NSArray!
commentSegments = inputString.componentsSeparatedByString(" ")
if (commentSegments.count > 0) {
for (var loop = 0; loop < commentSegments.count; loop++) {
// Check the username to ensure that there
// are no capital letters in the string.
let currentString = commentSegments[loop] as! String
let capitalLetterRegEx = ".*[A-Z]+.*"
let textData = NSPredicate(format:"SELF MATCHES %#", capitalLetterRegEx)
let capitalResult = textData.evaluateWithObject(currentString)
// Check if the current loop string
// is a #user mention string or not.
if (currentString.containsString("#")) {
// If we are in the first loop then set the
// string otherwise concatenate the string.
if (loop == 0) {
if (capitalResult == true) {
// The username contains capital letters
// so change it to a lower case version.
finalString = currentString.lowercaseString
}
else {
// The username does not contain capital letters.
finalString = currentString
}
}
else {
if (capitalResult == true) {
// The username contains capital letters
// so change it to a lower case version.
finalString = "\(finalString) \(currentString.lowercaseString)"
}
else {
// The username does not contain capital letters.
finalString = "\(finalString) \(currentString)"
}
}
}
else {
// The current string is NOT a #user mention
// so simply set or concatenate the finalString.
if (loop == 0) {
finalString = currentString
}
else {
finalString = "\(finalString) \(currentString)"
}
}
}
}
else {
// No issues pass back the string.
finalString = inputString
}
// Pass back the correct username string.
completion(correctString: finalString)
}
Its certainly not the most elegant or efficient solution around but it does work. If there are any ways of improving it, please leave a comment.

Swift RegEx International

I am checking for a valid birthday in a regEx after the user has entered their birthday in a date picker.
The issue is that if the phone is in Russian, Chinese, or any other language that uses other characters, the RegEx will not work due the month.
Is there an easy way to convert the month to digits (1-12) even though the month is displayed as letters and also work in any language?
If there was such a way, easy or not, it would be a lot easier to check if the user is exactly 13 years or older instead of just using the year.
func isValidBirthday(testStr4:String) -> Bool {
println("validate birthday: \(testStr4)")
let birthdayRegEx = "[A-Z0-9a-z, .]+(19[0-9][0-9]|200[0-3]|2003)"
let birthdayTest = NSPredicate(format: "SELF MATCHES %#", birthdayRegEx)
return birthdayTest.evaluateWithObject(testStr4)
}
I have a utilities class for checking Regex, using in a lot of my project
class Regex {
let internalExpression: NSRegularExpression!
let pattern: String
init(_ pattern: String) {
self.pattern = pattern
var error: NSError?
internalExpression = NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: &error)
}
func test(input: String) -> Bool {
if let matches = internalExpression?.matchesInString(input, options: nil, range:NSMakeRange(0, count(input))) {
return matches.count > 0
}
return false
}
}
For your case:
if Regex("^(19|20)\\d\\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$").test(DateStringValue) {
// Code to be executed in here
}
Hope it help.

Resources