Map array of objects to Dict with key as Enum - ios

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] ?? []

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

How to convert CBDescriptor Value to String?

I can able to read the descriptor value in the method "didUpdateValeFor descriptor:". Below is the log of the descriptor that I get from the BLE device.
<CBDescriptor: 0x1c445cb60, UUID = Characteristic Format, value = <08ff2427 013110>>
Values are in data format which is of some valid range from XX value to YY value and it is of type uint32 i guess. I couldn't able to convert them. I'm not able to get any solution from other answers.
In general, the CBDescriptor has a uuid property which will be one of the values specified in the documentation and a value property. The type of the value property will depend on the uuid. You can find the relevant value type in each of the possible CBDescriptor uuids. For example, for CBUUIDCharacteristicFormatString the value is a Data instance that encodes the format string.
A sample function that decodes a CBDescriptor is shown below. You would need to add the missing uuid types.
func descriptorDescription(for descriptor: CBDescriptor) -> String {
var description: String?
var value: String?
switch descriptor.uuid.uuidString {
case CBUUIDCharacteristicFormatString:
if let data = descriptor.value as? Data {
description = "Characteristic format: "
value = data.description
}
case CBUUIDCharacteristicUserDescriptionString:
if let val = descriptor.value as? String {
description = "User description: "
value = val
}
case CBUUIDCharacteristicExtendedPropertiesString:
if let val = descriptor.value as? NSNumber {
description = "Extended Properties: "
value = val.description
}
case CBUUIDClientCharacteristicConfigurationString:
if let val = descriptor.value as? NSNumber {
description = "Client characteristic configuration: "
value = val.description
}
case CBUUIDServerCharacteristicConfigurationString:
if let val = descriptor.value as? NSNumber {
description = "Server characteristic configuration: "
value = val.description
}
case CBUUIDCharacteristicAggregateFormatString:
if let val = descriptor.value as? String {
description = "Characteristic aggregate format: "
value = val
}
default:
break
}
if let desc=description, let val = value {
return "\(desc)\(val)"
} else {
return "Unknown descriptor"
}
}
For the specific decoding of the data associated with CBUUIDCharacteristicFormatString you need to refer to the Bluetooth documentation

If let condition true when value is missing in optional type, swift

I have parser in Objc, parser returns NSDictionary. I am using this parser in swift class. But when some value is missing on that dictionary, it shows nil value. e.g. ->
wirlessData = {
"anon" = {
};
"channel" = {
"text" = 1;
};
}
I am checking through
if let wepauthValue = wirlessData["wepauth"] {
if let value = wepauthValue["text"] {
print("\(value)") // nil
}
}
I don't how it satisfy the if let condition. Any one faced this types of problem can help me out.
Thanks,
vikash
You don't need any special code to do this, because it is what a dictionary already does. When you fetch dict[key] you know whether the dictionary contains the key, because the Optional that you get back is not nil (and it contains the value).
So, if you just want to answer the question whether the dictionary contains the key, ask:
let keyExists = dict[key] != nil
If you want the value and you know the dictionary contains the key, say:
let val = dict[key]!
But if, as usually happens, you don't know it contains the key - you want to fetch it and use it, but only if it exists - then use something like if let:
if let val = dict[key] {
// now val is not nil and the Optional has been unwrapped, so use it
}
I have tested it and found that value is still optional.Take a look at screenshot below to understand it better.
"anon" would be an empty dictionary. An empty dictionary is not nil, it is a dictionary. Just an empty one. A JSON parser will never, ever give nil values unless you ask for a key that is not in a dictionary. For example wirlessData ["nonexistingkey"] would give you nil.
If you be more type-strong about it with the if..let's then:
if let anonValue = wirlessData["anon"] {
if let value = anonValue["text"] as? String {
// This won't execute if value isn't converted from `anonvalue["text"]` to String specifically. This includes null been a false match too
print("\(value)") // nil
}else{
print("Value did't match string at all")
}
}
or even more specifically in your case:
if let anonValue = wirlessData["anon"] {
if let value = anonValue["text"] as? Int {
// This won't execute if value isn't converted from `anonvalue["text"]` to String specifically. This includes null been a false match too
print("\(value)") // nil
}else{
print("Value did't match int at all")
}
}
The value your parser is returning not nil, its empty so you need to check on count if inner data type is dictionary or array, I have past 1 sample here
Please use below code and correct your logic accordingly to get it work properly
let wirlessData:[String:AnyObject] = [
"anon" : [],
"channel" : [
"text" : 1
]
]
if wirlessData["anon"]?.count > 0 {
if let value = wirlessData["anon"]!["text"] {
print("\(value)") // nil
}
}
Try this below code using type check operator (is) -
if wirlessData["anon"] is [String:AnyObject]
{
let anon = wirlessData["anon"]!
print(anon)
if anon["random"] is String {
let stringValue = anon["random"]!
print("\(stringValue)")
}
else if anon["random"] is Int
{
let intValue = anon["random"]!
print("\(intValue)") // nil
}
else
{
print(" may be value did't match string & Int or nil ")
}
}

Swift filter array of strings

I've had troubles filtering array of keywords (strings) in swift ,My code:
self.filteredKeywords=filter(keywords.allValues, {(keyword:NSString) ->
Bool in
let words=keyword as? NSString
return words?.containsString(searchText)
})
As AnyObject can't be subtype of NSString, I'm stuck with this!
[Updated for Swift 2.0]
As NSString is toll-free bridged to Swift String, just avoid the coercions with:
3> ["abc", "bcd", "xyz"].filter() { nil != $0.rangeOfString("bc") }
$R1: [String] = 2 values {
[0] = "abc"
[1] = "bcd"
}
But, if you think allValues aren't strings:
(keywords.allValues as? [String]).filter() { nil != $0.rangeOfString("bc") }
which returns an optional array.
Your filter is over [AnyObject], but your closure takes NSString. These need to match. Also, your result needs to be a Bool, not a Bool?. You can address these simply like this:
self.filteredKeywords = filter(keywords.allValues, {
let keyword = $0 as? NSString
return keyword?.containsString(searchText) ?? false
})
This accepts AnyObject and then tries to coerce it down to NSString. It then nil-coalleces (??) the result to make sure it always is a Bool.
I'd recommend, though, treating keywords as a [String:String] rather than an NSDictionary. That would get rid of all the complications of AnyObject. Then you can just do this:
self.filteredKeywords = keywords.values.filter { $0.rangeOfString(searchText) != nil }
Whenever possible, convert Foundation collections into Swift collections as soon as you can and store those. If you have incoming Foundation objects, you can generally convert them easily with techniques like:
let dict = nsdict as? [String:String] ?? [:]
Or you can do the following to convert them such that they'll crash in debug (but silently "work" in release):
func failWith<T>(msg: String, value: T) -> T {
assertionFailure(msg)
return value
}
let dict = nsdict as? [String:String] ?? failWith("Couldn't convert \(d)", [:])
Swift 4.2 provides a new way to do this:
var theBigLebowski = ["The Dude", "Angry Walter", "Maude Lebowski", "Donny Kerabatsos", "The Big Lebowski", "Little Larry Sellers"]
// after removeAll -> ["The Dude", "Angry Walter", "Donny Kerabatsos", "Little Larry Sellers"]
theBigLebowski.removeAll{ $0.contains("Lebowski")}
print(theBigLebowski)
There is both a problem with GoZoner's answer for certain data types and also a slightly better way to do this. The following examples can show this:
let animalArray: NSMutableArray = ["Dog","Cat","Otter","Deer","Rabbit"]
let filteredAnimals = animalArray.filter { $0.rangeOfString("er") != nil }
print("filteredAnimals:", filteredAnimals)
filteredAnimals: [Dog, Cat, Otter, Deer, Rabbit]
Likely not the set you expected!
However this works fine this way if we don't type animalArray as an NSMutableArray:
let animalArray = ["Dog","Cat","Otter","Deer","Rabbit"]
let filteredAnimals = animalArray.filter { $0.rangeOfString("er") != nil }
print("filteredAnimals:", filteredAnimals)
filteredAnimals: [Otter, Deer]
However I'd recommend using $0.contains() instead of $0.rangeOfString() != nil because it functions in both circumstances and slightly enhances the readability of the code:
let animalArray: NSMutableArray = ["Dog","Cat","Otter","Deer","Rabbit"]
let filteredAnimals = animalArray.filter { $0.contains("er") }
print("filteredAnimals:", filteredAnimals)
filteredAnimals: [Otter, Deer]

Detect a Null value in NSDictionary

I have an NSDictionary that's populated from a JSON response from an API server. Sometimes the values for a key in this dictionary are Null
I am trying to take the given value and drop it into the detail text of a table cell for display.
The problem is that when I try to coerce the value into an NSString I get a crash, which I think is because I'm trying to coerce Null into a string.
What's the right way to do this?
What I want to do is something like this:
cell.detailTextLabel.text = sensor.objectForKey( "latestValue" ) as NSString
Here's an example of the Dictionary:
Printing description of sensor:
{
"created_at" = "2012-10-10T22:19:50.501-07:00";
desc = "<null>";
id = 2;
"latest_value" = "<null>";
name = "AC Vent Temp";
"sensor_type" = temp;
slug = "ac-vent-temp";
"updated_at" = "2013-11-17T15:34:27.495-07:00";
}
If I just need to wrap all of this in a conditional, that's fine. I just haven't been able to figure out what that conditional is. Back in the Objective-C world I would compare against [NSNull null] but that doesn't seem to be working in Swift.
You can use the as? operator, which returns an optional value (nil if the downcast fails)
if let latestValue = sensor["latestValue"] as? String {
cell.detailTextLabel.text = latestValue
}
I tested this example in a swift application
let x: AnyObject = NSNull()
if let y = x as? String {
println("I should never be printed: \(y)")
} else {
println("Yay")
}
and it correctly prints "Yay", whereas
let x: AnyObject = "hello!"
if let y = x as? String {
println(y)
} else {
println("I should never be printed")
}
prints "hello!" as expected.
You could also use is to check for the presence of a null:
if sensor["latestValue"] is NSNull {
// do something with null JSON value here
}
I'm using this combination and it also checks if object is not "null".
func isNotNull(object: AnyObject?) -> Bool {
guard let object = object else { return false }
return isNotNSNull(object) && isNotStringNull(object)
}
func isNotNSNull(object: AnyObject) -> Bool {
object.classForCoder != NSNull.classForCoder()
}
func isNotStringNull(object: AnyObject) -> Bool {
guard let object = object as? String where object.uppercaseString == "NULL" else {
return true
}
return false
}
It's not that pretty as extension but work as charm :)
NSNull is a class like any other. Thus you can use is or as to test an AnyObject reference against it.
Thus, here in one of my apps I have an NSArray where every entry is either a Card or NSNull (because you can't put nil in an NSArray). I fetch the NSArray as an Array and cycle through it, switching on which kind of object I get:
for card:AnyObject in arr {
switch card { // how to test for different possible types
case let card as NSNull:
// do one thing
case let card as Card:
// do a different thing
default:
fatalError("unexpected object in card array") // should never happen!
}
}
That is not identical to your scenario, but it is from a working app converted to Swift, and illustrates the full general technique.
my solution for now:
func isNull(someObject: AnyObject?) -> Bool {
guard let someObject = someObject else {
return true
}
return (someObject is NSNull)
}
tests look good so far...
I had a very similar problem and solved it with casting to the correct type of the original NSDictionary value. If your service returns a mixed type JSON object like this
{"id":2, "name":"AC Vent Temp", ...}
you'll have to fetch it's values like that.
var id:int = sensor.valueForKey("id") as Int;
var name:String? = sensor.valueForKey("name") as String;
This did solve my problem. See BAD_INSTRUCTION within swift closure

Resources