How to use subscripts in an Optional Array of Dictionary - swift

I am attempting to read a csv file on linux using the CSwiftV library, however the returned type that interests me the most is an optional array of dictionaries. I have been struggling to understand how to use subscripting to access the contents of the Array. Using the library most basic example (if you have marathon installed, just copy on a file and marathon run):
import CSwiftV // marathon: https://github.com/Daniel1of1/CSwiftV.git
let inputString = "Year,Make,Model,Description,Price\r\n1997,Ford,E350,descrition,3000.00\r\n1999,Chevy,Venture,another description,4900.00\r\n"
let csv = CSwiftV(with: inputString)
let rows = csv.rows // [
// ["1997","Ford","E350","descrition","3000.00"],
// ["1999","Chevy","Venture","another description","4900.00"]
// ]
let headers = csv.headers // ["Year","Make","Model","Description","Price"]
let keyedRows = csv.keyedRows // [
// ["Year":"1997","Make":"Ford","Model":"E350","Description":"descrition","Price":"3000.00"],
// ["Year":"1999","Make":"Chevy","Model":"Venture","Description":"another, description","Price":"4900.00"]
// ]
print(csv.rows)
print(csv.headers)
print(csv.keyedRows)
So far so good, but now when I try print(csv.keyedRows[0][0]) or print(csv.keyedRows[[0]]) I get something like:
- 24:16: value of optional type '[[String : String]]?' must be unwrapped to refer to member 'subscript' of wrapped base type '[[String : String]]'
How can I access the dictionary data in this case (I am thinking on something similar to csv.keyedRows[0]["Year"]) ?

For this:
print(csv.keyedRows[0]["Year"])
You can use optional binding:
if let keyedRows = csv.keyedRows {
print(keyedRows[0]["Year"])
} else {
// keyedRows is nil!
}
Or you can use the postfix ? operator:
print(csv.keyedRows?[0]["Year"] as Any)
// or
print(csv.keyedRows?[0]["Year"] ?? "")

Related

Filter An Array Of Arrays By A Value In Firestore Swift

I'm looking to filter an array of arrays by specific value of one of the keys located within each array. Each nested array is read in from Firestore.
As an object, each nested array would look like this:
struct Video {
var url: String
var submissionDate: Timestamp
var submittingUser: String
}
I'm reading it in like this:
videos = document.get("videos") as? [[String : Any]] ?? nil
So far so good, but when I filter it like this:
filteredVideos = videos.filter { $0[2].contains(self.userIdentification) }
I can't do it without getting the error "Reference to member 'contains' cannot be resolved without a contextual type," an error which I was unable to find any relevant information on SO about.
I have read that some people say "Don't use arrays in Firestore!" but this is a build requirement.
Anyone have any ideas? Basically just need all arrays within the array where userId == submittingUser.
Reference Article:
I tried the answer from here: How to filter out a Array of Array's but no luck for this situation.
It's actually an array of dictionaries, not an array of arrays. All you need to do is construct the right predicate for the filter.
This is basically what your Firestore data looks like:
let videos: [[String: Any]] = [
["url": "http://www...", "submittingUser": "user1"],
["url": "http://www...", "submittingUser": "user2"]
]
This is the user you're looking for:
let userIdentification = "user2"
This is the predicate for the filer:
let filteredVideos = videos.filter { (video) -> Bool in
if let submittingUser = video["submittingUser"] as? String,
submittingUser == userIdentification {
return true
} else {
return false
}
}
You can shorthand this down to a single line if you're okay with force-unwrapping the dictionary (if you're 100% certain every video will have a valid submittingUser value):
let filteredVideos = videos.filter({ $0["submittingUser"] as! String == userIdentification })

How to pass and get multiple URLQueryItems in Swift?

Ok, I am working in an iMessage app and am trying to parse more than 1 url query item from the selected message here- I have been successful getting/sending just 1 value in a query:
override func willBecomeActive(with conversation: MSConversation) {
// Called when the extension is about to move from the inactive to active state.
// This will happen when the extension is about to present UI.
if(conversation.selectedMessage?.url != nil) //trying to catch error
{
let components = URLComponents(string: (conversation.selectedMessage?.url?.query?.description)!)
//let val = conversation.selectedMessage?.url?.query?.description
if let queryItems = components?.queryItems {
// process the query items here...
let param1 = queryItems.filter({$0.name == "theirScore"}).first
print("***************=> GOT IT ",param1?.value)
}
}
When I just have 1 value, just by printing conversation.selectedMessage?.url?.query?.description I get an optional with that 1 value, which is good. But with multiple I cant find a clean way to get specific values by key.
What is the correct way to parse a URLQueryItem for given keys for iMessage?
When you do conversation.selectedMessage?.url?.query?.description it simply prints out the contents of the query. If you have multiple items then it would appear something like:
item=Item1&part=Part1&story=Story1
You can parse that one manually by splitting the string on "&" and then splitting the contents of the resulting array on "=" to get the individual key value pairs in to a dictionary. Then, you can directly refer to each value by key to get the specific values, something like this:
var dic = [String:String]()
if let txt = url?.query {
let arr = txt.components(separatedBy:"&")
for item in arr {
let arr2 = item.components(separatedBy:"=")
let key = arr2[0]
let val = arr2[1]
dic[key] = val
}
}
print(dic)
The above gives you an easy way to access the values by key. However, that is a bit more verbose. The way you provided in your code, using a filter on the queryItems array, is the more compact solution :) So you already have the easier/compact solution, but if this approach makes better sense to you personally, you can always go this route ...
Also, if the issue is that you have to write the same filtering code multiple times to get a value from the queryItems array, then you can always have a helper method which takes two parameters, the queryItems array and a String parameter (the key) and returns an optional String value (the value matching the key) along the following lines:
func valueFrom(queryItems:[URLQueryItem], key:String) -> String? {
return queryItems.filter({$0.name == key}).first?.value
}
Then your above code would look like:
if let queryItems = components?.queryItems {
// process the query items here...
let param1 = valueFrom(queryItems:queryItems, key:"item")
print("***************=> GOT IT ", param1)
}
You can use iMessageDataKit library. It makes setting and getting data really easy and straightforward like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "user_id")
message.md.set(value: "john", forKey: "username")
message.md.set(values: ["joy", "smile"], forKey: "tags")
print(message.md.integer(forKey: "user_id")!)
print(message.md.string(forKey: "username")!)
print(message.md.values(forKey: "tags")!)
(Disclaimer: I'm the author of iMessageDataKit)

Swift 3: Change item in dictionary

I'm saving lists in a dictionary. These lists need to be updated. But when searching for an item, I need [] operator. When I save the result to a variable, a copy is used. This can not be used, to change the list itself:
item = dicMyList[key]
if item != nil {
// add it to existing list
dicMyList[key]!.list.append(filename)
// item?.list.append(filename)
}
I know, that I need the uncommented code above, but this accesses and searches again in dictionary. How can I save the result, without searching again? (like the commented line)
I want to speed up the code.
In case you needn't verify whether the inner list was actually existing or not prior to adding element fileName, you could use a more compact solution making use of the nil coalescing operator.
// example setup
var dicMyList = [1: ["foo.sig", "bar.cc"]] // [Int: [String]] dict
var key = 1
var fileName = "baz.h"
// "append" (copy-in/copy-out) 'fileName' to inner array associated
// with 'key'; constructing a new key-value pair in case none exist
dicMyList[key] = (dicMyList[key] ?? []) + [fileName]
print(dicMyList) // [1: ["foo.sig", "bar.cc", "baz.h"]]
// same method used for non-existant key
key = 2
fileName = "bax.swift"
dicMyList[key] = (dicMyList[key] ?? []) + [fileName]
print(dicMyList) // [2: ["bax.swift"], 1: ["foo.sig", "bar.cc", "baz.h"]]
Dictionaries and arrays are value types. So if you change an entry you'll need to save it back into the dictionary.
if var list = dicMyList[key] {
list.append(filename)
dicMyList[key] = list
} else {
dicMyList[key] = [filename]
}
It's a little bit late, but you can do something like this:
extension Optional where Wrapped == Array<String> {
mutating func append(_ element: String) {
if self == nil {
self = [element]
}
else {
self!.append(element)
}
}
}
var dictionary = [String: [String]]()
dictionary["Hola"].append("Chau")
You can try this in the Playground and then adapt to your needs.

Swift Optional Dictionary [String: String?] unwrapping error

So here I have a basic setup
var preferenceSpecification = [String : String?]()
preferenceSpecification["Key"] = "Some Key"
preferenceSpecification["Some Key"] = nil
preferenceSpecification["DefaultValue"] = "Some DefaultValue"
print(preferenceSpecification)
var defaultsToRegister = [String : String]()
if let key = preferenceSpecification["Key"], let defaultValueKey = preferenceSpecification["DefaultValue"] {
defaultsToRegister[key] = preferenceSpecification[defaultValueKey]!
}
But the error points out where it demands that I force unwrap this, to be like this:
defaultsToRegister[key!] = preferenceSpecification[defaultValueKey!]!
Which doesn't make sense, because keyValue and defaultValue already are unwrapped
When you extract a value from a dictionary like this using subscript
[String: String?]
you need to manage 2 levels of optional. The first one because the subscript returns an optional. The second one because the value of you dictionary is an optional String.
So when you write
if let value = preferenceSpecification["someKey"] {
}
you get value defined as an optional String.
Here's the code to fix that
if let
optionalKey = preferenceSpecification["Key"],
key = optionalKey,
optionalDefaultValueKey = preferenceSpecification["DefaultValue"],
defaultValueKey = optionalDefaultValueKey,
value = preferenceSpecification[defaultValueKey] {
defaultsToRegister[key] = value
}
Suggestions
You should avoid force unwrapping as much as possible. Instead you managed to put 3 ! on a single line!
You should also try to use better name for your constants and variables.
You could also define an extension which helps get rid of the double optional situation.
extension Dictionary where Value == Optional<String> {
func flattened(_ key: Key) -> Value {
if let value = self[key] {
return value
}
return nil
}
}
Usage: preferenceSpecification.flattened("someKey")

Ambiguous reference to member 'joinWithSeparator' in swift

I'm using Google's 'reverseGeocodeCoordinate' to get address based on latitude and longitude.
I'm getting the following error in the implementation
Ambiguous reference to member 'joinWithSeparator'
Below is my implementation:
let aGMSGeocoder: GMSGeocoder = GMSGeocoder()
aGMSGeocoder.reverseGeocodeCoordinate(CLLocationCoordinate2DMake(17.45134626, 78.39304448)) {
(let gmsReverseGeocodeResponse: GMSReverseGeocodeResponse!, let error: NSError!) -> Void in
let gmsAddress: GMSAddress = gmsReverseGeocodeResponse.firstResult()
print("lines=\(gmsAddress.lines)")
let addressString = gmsAddress.lines.joinWithSeparator("")
print("addressString=\(addressString)")
}
I'm trying to create a addressString with the elements in array 'gmsAddress.lines', but end up with an error message.
Implemented some sample snippet to test 'joinWithSeparator'
let sampleArray = ["1", "2", "3", "4", "5"]
let joinedString = sampleArray.joinWithSeparator("")
print("joinedString=\(joinedString)")
It succeeded.
What I observe is, 'sampleArray' is an array of elements of type String, but 'gmsAddress.lines' is an array of elements of type 'AnyObject', Found in 'GMSAddress' library:
/** An array of NSString containing formatted lines of the address. May be nil. */
public var lines: [AnyObject]! { get }
So, What are the possible ways to achieve the following line without looping the array:
let addressString = gmsAddress.lines.joinWithSeparator("")
It is ambiguous because the array can contain AnyObject meaning every object in the array could be of a different type. Therefore the compiler can't know in advance whether any two objects in the array can be joined.
The reason your sampleArray works is that it has been implicitly determined to be an array of strings.
If you know for a fact that every element in the lines array is a string you can force downcast it to an array of strings:
let addressString = (gmsAddress.lines as! [String]).joinWithSeparator("")
Though it's probably worthwhile being safe about this and checking first.
if let lines = gmsAddress.lines as? [String] {
let addressString = lines.joinWithSeparator(", ")
...
}