SwiftUI Can't Pass String to KeyPath - ios

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"))

Related

Map array of objects to Dict with key as Enum

I got a dict of enum as key and values as object
#Published var subscriptionProducts = [SubscriptionType: SKProduct]()
enum SubscriptionType {
case monthly = "uniqueID"
case annualy = "uniqueID"
}
I got another array of type SKProduct
I want to assign it to my dict with a property of each object as key, and the object itself as value
Trying
subscriptionProducts = Dictionary(uniqueKeysWithValues:products.map({$0.productIdentifier , $0 }))
But I got
Type of expression is ambiguous without more context
the productIdentifier is of type String, how to assign it to the rawValue of my enum?
The result I want to achieve later is to get the specific object using that key
let monthlySubscriptionProduct = subscriptionProducts[.monthly]
You need to first declare your SubscriptionType enumeration RawValue as String. Then you will need to convert your productIdentifier String value to SubscriptionType using its fallible init(rawValue:) initializer. Something like:
enum SubscriptionType: String {
case monthly, annualy
}
let subscriptionProducts: [SubscriptionType: SKProduct] = Dictionary(uniqueKeysWithValues: products.compactMap {
guard let subscriptionType = SubscriptionType(rawValue: $0.productIdentifier) else {
return nil
}
return (subscriptionType, $0)
})
let monthlySubscriptionProduct = subscriptionProducts[.monthly]
But IMO that's not what you really want. Looks like what you are trying to accomplish is to group all monthly subscriptions which can be done using reduce as follow:
let subscriptionProducts: [SubscriptionType: [SKProduct]] = products.reduce(into: [:]) {
guard let subscriptionType = SubscriptionType(rawValue: $1.productIdentifier) else {
return
}
$0[subscriptionType, default: []].append($1)
}
let monthlySubscriptions = subscriptionProducts[.monthly] ?? []

How to update Firebase with struct

I have a struct EventDate and am trying to update a reference in Firebase.
struct EventDate {
var club: String = ""
var date: String = ""
var eventText: String = ""
var free: String = ""
var monthYear: String = ""
}
My update function throws lldb. I guess because the keys are no Strings(?)
func updateEvent(_ backendlessUserObjectID: String, event: EventDate) {
let reference = firebase.child("eventDates").child(backendlessUserObjectID).child(event.date)
reference.setValue(event) { (error, ref) -> Void in
if error != nil {
print("\(error)")
}
} // lldb here
}
If I change the function to the following, everything is fine (because Keys and Values are now Strings?)
func updateEvent(_ backendlessUserObjectID: String, event: EventDate) {
let item: NSMutableDictionary = ["club" : event.club,
"date" : event.date,
"eventText" : event.eventText,
"free" : event.free,
"monthYear" : event.monthYear]
let reference = firebase.child("eventDates").child(backendlessUserObjectID).child(event.date)
reference.setValue(item) { (error, ref) -> Void in
if error != nil {
print("\(error)")
}
}
}
Am I right that I receive lldb because the keys from my models are not Strings? Or what am I missing and how will I be able to save the values into my Firebase using my model without creating the NSMutableDictionary? Help is very appreciated.
PS: print(event.date) = 201610120200000000 -> the desired value for .child
Firebase data exists in a JSON format which can be thought of as key:value pairs. The keys must be strings and the values can be any of the for data types mentioned in Dravidians answer (which is a correct answer and should be accepted). I would like to add some additional comments that may help as well.
There are times when you want to use a structure in code and that can't be written to Firebase directly - you need some way to get the data out of the structure into something Firebase likes - which is a Dictionary.
Heres an example (Swift 3, Firebase 2.x)
struct EventDate {
var club: String = ""
var date: String = ""
var eventText: String = ""
var free: String = ""
var monthYear: String = ""
func getDict() -> [String:String] {
let dict = ["club": self.club,
"date": self.date,
"eventText": self.eventText,
"free": self.free,
"monthYear": self.monthYear
]
return dict
}
}
var event = EventDate()
event.club = "Wimbledon"
event.date = "20161023"
event.eventText = "Special Tennis Event"
event.free = "Free"
event.monthYear = "10/2016"
let ref = self.myRootRef.child(byAppendingPath: "events")!
let eventRef = ref.childByAutoId() //Firebase 2.x
eventRef?.setValue( event.getDict() )
This results in a node being written to Firebase that looks like this
"events" : {
"-KUli8oiM_KKw8GZ0MMm" : {
"club" : "Wimbeldon",
"date" : "20161023",
"eventText" : "Special Tennis Event",
"free" : "Free",
"monthYear" : "10/2016"
}
}
No it has nothing to do with the type of keys that you are trying to save in your Firebase Database its just that struct is a dataModel or to be precise a physically grouped list of variables which you initialise with some custom Data, and you can only save four types of values types in your Firebase Database:-
NSDictionary
NSArray
NSNumber
NSString
Look up the docs :- Read And Write data, Firebase- iOS
So when you cast your values in a NSMutableDictionary, you come clean of struct. And struct and class is not recognisable by the Firebase Database.

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

swift text with 2 variables and different language

i would like to translate a string, which have two variables inside.
at the moment, i use for translating this code:
NSLocalizedString("Name_In_Langauge_String_File",comment:"")
but how can i translate the following string?
This is a test with 100 Pictures and 50 Users
where 100 and 50 are variables.
Put this in you Localizable.strings:
"Name_In_Langauge_String_File" = "This is a test with %d Pictures and %d Users";
and in your code:
String.localizedStringWithFormat(
NSLocalizedString("Name_In_Langauge_String_File",
comment: ""),
pictures,
users)
In a project I was working on I noticed that we kept repeating the code to do the string formatting for the localization file. This meant you could not just use the value, you first needed to check what parameters were required. One way to avoid this problem is to use Swift enums. This method is also useful for unit testing your localizations.
Assume you have the following 3 localizations in your strings file:
"TestNoParams" = "This is a test message";
"TestOneParam" = "Hello %#";
"TestTwoParams" = "This is a test with %d Pictures and %d Users";
Now you can use the following enum, protocol and extension to reference your strings:
protocol LocalizationProtocol {
var key: String { get }
var value: String { get }
}
extension LocalizationProtocol {
private func localizationValue() -> String {
return NSLocalizedString(key, comment:key)
}
private func localizationValueWithFormat(parameters: CVarArgType...) -> String {
return String(format: localizationValue(), arguments: parameters)
}
}
enum Localizations: LocalizationProtocol {
case TestNoParams
case TestOneParam(name: String)
case TestPicturesAndUsers(pictures: Int, users: Int)
var key: String {
switch self {
case .TestNoParams: return "TestNoParams"
case .TestOneParam: return "TestOneParam"
case .TestPicturesAndUsers: return "TestTwoParams"
}
}
var value: String {
switch self {
case .TestOneParam(let name):
return localizationValueWithFormat(name)
case .TestPicturesAndUsers(let pictures, let users):
return localizationValueWithFormat(pictures, users)
default:
return localizationValue()
}
}
}
Now to use it you just need to call the enums value method:
let testNoParam = Localizations.TestNoParams.value
let testOneParam = Localizations.TestOneParam(name: "users name").value
let testTwoParams = Localizations.TestPicturesAndUsers(pictures: 4, users: 500).value
The example I have shown is simplified, but you can also nest enums to provide a nice grouping for your localizations. For instance you could have your enums nested by ViewController. This is an example for a welcome message: Localizations.Main.WelcomeMessage.value

Short Random Unique alphanumeric keys similar to Youtube IDs, in Swift

Is there a way to create random unique IDs similar to the YouTube IDs in Swift?
I know there are similar answers on this link, but they are for Php. But I want something in Swift.
I have tried using timestamp and UUIDs, but I want an alphanumeric short keys which would be around 4-10 characters so users can easily share with others verbally.
Thanks.
Looking for just a unique string
You can use UUIDs they're pretty cool:
let uuid = NSUUID().UUIDString
print(uuid)
From the  docs
UUIDs (Universally Unique Identifiers), also known as GUIDs (Globally
Unique Identifiers) or IIDs (Interface Identifiers), are 128-bit
values. UUIDs created by NSUUID conform to RFC 4122 version 4 and are
created with random bytes.
Some info about uuid: https://en.wikipedia.org/wiki/Universally_unique_identifier
Looking for a more specific length
Try something like this:
func randomStringWithLength(len: Int) -> NSString {
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let randomString : NSMutableString = NSMutableString(capacity: len)
for _ in 1...len{
let length = UInt32 (letters.length)
let rand = arc4random_uniform(length)
randomString.appendFormat("%C", letters.character(at: Int(rand)))
}
return randomString
}
But i'll keep my answer incase someone else stumbles upon this looking for a UUID
This will allow you to create a random short code. It can create codes from Hexadecimal all the way to base 62 codes and of varying lengths.
aka.
let myCode = ShortCodeGenerator.getCode(length: 6)
3dH7t8,
fdE7j1,
Gl6jKd,
Hv8gU3,
let myBase32Code = ShortCodeGenerator.getCode(withBase: UInt32(32), length: 6)
3HF75J,
J67N9D,
B47SO3,
L9SD2N
You would have to check for redundancy and then create a new one if it has already been used.
struct ShortCodeGenerator {
private static let base62chars = [Character]("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".characters)
private static let maxBase : UInt32 = 62
static func getCode(withBase base: UInt32 = maxBase, length: Int) -> String {
var code = ""
for _ in 0..<length {
let random = Int(arc4random_uniform(min(base, maxBase)))
code.append(base62chars[random])
}
return code
}
}
This answer was useful in creating the above code and also has good
information on the number of unique identifiers you can have with each base number and length.
The total number of unique identifiers you need can be calculated by the equation:
BaseNumber^length = # of unique IDs
EDIT:
I have added even more functionality for converting Int's and NSDate's to shortcodes for my own project as well and put those into a project on GitHub.
Updated for swift 3:
If you want to generate Short Random Unique alphanumeric keys, used below lines of codes;
//function defination:
func generateTransactionId(length: Int) -> String {
var result = ""
let base62chars = [Character]("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".characters)
let maxBase : UInt32 = 62
let minBase : UInt16 = 32
for _ in 0..<length {
let random = Int(arc4random_uniform(UInt32(min(minBase, UInt16(maxBase)))))
result.append(base62chars[random])
}
return result
}
//function call:
let mTranactionId = self.generateTransactionId(length: 5) // you can change the value of length as you want
print("mTranactionId: \(mTranactionId)")
Ex: Result looks like: XHA7K, QTC92, MS1PT, YE2PV
//Enjoy coding...!
Does NSUUID().UUIDString do what you need?
I'm very pleased with NanoID.
https://github.com/ai/nanoid
...and here's the SWIFT version:
https://github.com/antiflasher/NanoID
You just need to drag 1 file (NanoID.swift) into your project, and you are good to go!
TAGS: Short GUID, Short UUID, Short Unique ID, Swift, iOS
Swift custom alternative(these version doesn't check for duplicate char):
func randomString(length: Int) -> String {
let letters = Array("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
var randomString: String = ""
for _ in 0..<length {
let randomNumber = Int.random(in: 0..<letters.count)
randomString.append(letters[randomNumber])
}
return randomString
}
You can archive this just using UUID.
If you don't want the whole string, as any String in Swift, you select a small portion of the string by using range, like this:
let text = UUID().uuidString
let index = text.index(text.startIndex, offsetBy: 8)
let small = text[text.startIndex..<index]
Notice 8 is the length of string I suggested, you can improve this by clamping this value using min(size, text.count) for example.
And finally, small is a Substring, to cast it to String just cast as usual - String(small)
Swift 5 version using a String extension
extension String {
static func random(length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).compactMap { _ in
letters.randomElement()
})
}
}

Resources